home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1995 July / macformat-026.iso / mac / Shareware City / Developers / PopupCDEF-10b5 / Source / PopupLib.c < prev    next >
Encoding:
Text File  |  1994-11-30  |  75.9 KB  |  2,525 lines  |  [TEXT/KAHL]

  1. /* See the file Distribution for distribution terms.
  2.     (c) Copyright 1994 Ari Halberstadt */
  3.  
  4. /*    Functions implementing a popup menu. To create a popup call
  5.     PopupBegin and then PopupCurrentSet to set the initial item.
  6.     Use PopupCurrent to find out which item was selected. There
  7.     are quite a few other functions for controling how the popup
  8.     is displayed: whether the popup is drawn, whether the popup
  9.     is visible, enabling and disabling the popup, whether to
  10.     use the window font to display the popup, setting the text
  11.     style for the title, and more. The defaults are set by
  12.     PopupBegin to be those recommended by Apple in IM-VI. This
  13.     library is the basis for a popup menu 'CDEF'.
  14.     
  15.     You can use the popup CDEF to create a popup menu. The control's
  16.     contrlData field will contain a handle to the popup. If you need to
  17.     directly modify the popup, you can call the popup library routines
  18.     on the handle. However, first call PopupVersion and make sure it
  19.     is equal to kPopupVersion. If it isn't, then don't use the popup
  20.     library to modify the control. In practice, it is better to use
  21.     Control Manager routines to modify the popup.
  22.     
  23.     If you implement keyboard equivalents of menu commands, then you can
  24.     use PopupHilite to hilite the title of the menu while executing the
  25.     menu command.
  26.     
  27.     94/11/26 aih
  28.     - Supports control color tables.
  29.     - Popup title colors are swapped when hiliting the
  30.     title. This is how the system MDEF and other CDEFs
  31.     behave. The title is simply inverted when drawing in
  32.     black and white.
  33.     - Removed support for old headers (pre-Universal Headers).
  34.     
  35.     94/09/08 aih
  36.     - Fixed disabling of items > 15.
  37.     
  38.     94/07/16 aih
  39.     - Fixed almost all color problems.
  40.     - Improved right justification of menus, depending on system
  41.     justification value.
  42.     
  43.     94/07/12 aih
  44.     - Adapted for color QuickDraw and offscreen graphics worlds.
  45.     - Added support for color menu entries.
  46.     
  47.     94/07/06 aih
  48.     - Adapted for universal headers
  49.     
  50.     94/03/15 aih
  51.     - The current selection will display items with small icons properly.
  52.     Formerly, this would display a large icon for items that had small or
  53.     reduced icons, and wouldn't display the large icon once a different icon
  54.     had been selected. Reduced icons still aren't displayed in the current
  55.     selection, and I'm not sure why not, since I copy the command key character
  56.     for reduced icons (0x1D) to the menu item used to draw the current
  57.     selection.
  58.     - When the useWFont variation code is used, the menu will be drawn in
  59.     the font and size of the popup menu's port. Formerly, using the useWFont
  60.     variation code produced incorrect results, but I fixed this by setting the
  61.     system font and size low-memory globals instead of trying to change the
  62.     font and size of the Window Manager's port.
  63.     - The currently selected item is drawn in gray if it is disabled. Formerly,
  64.     the item was only drawn in gray if it was disabled and if the menu item
  65.     was drawn using the MDEF.
  66.     - Updated "To Do" list.
  67.     - Clicking in the popup's title will also pull down the menu. This
  68.     is consistent with the operation of the system 7 popup CDEF.
  69.     - Added support for the popupFixedWidth variation code.
  70.     
  71.     93/12/28 aih
  72.     - Continued cleaning up code, added some more comments.
  73.     - Uses menu definition function to draw the current selection. This
  74.     eliminated a couple of rectangles from the popup structure and
  75.     means that the current selection will always be drawn correctly,
  76.     even if it includes icons and command keys. A special one item menu
  77.     is used to hold a copy of the currently selected menu item. This special
  78.     menu is then used to calculate the height of the item and to draw the
  79.     item.
  80.     
  81.     93/12/26 aih
  82.     - Major overhaul. Rewrote rectangle calculation code, since it wasn't
  83.     working right. It's still not as simple as I'd like, but it's better
  84.     than before. Also added attributes to make more compatible with Apple's
  85.     popup CDEF (such as using the window font to display the menu).
  86.     
  87.     93/12/24 aih
  88.     - Converted to use handles.
  89.     - Removed dependence on all other libraries, so that this library
  90.     is completely self contained; the few external functions that were
  91.     necessary (e.g., strcpy, TruncatePString) were copied or coded directly into
  92.     this file. This will make this library more suitable for use as a
  93.     CDEF since the linker won't pull in a lot of extra code from other
  94.     libraries.
  95.     
  96.     93/12/23 aih
  97.     - updated for current version of libraries
  98.     - added port parameter to PopupBegin
  99.     
  100.     91/11/11 aih
  101.     - Removed useless "PopupObj..." stuff
  102.     
  103.     91/03/01-05 aih
  104.     - Update events are handled
  105.     - Added comment describing this file
  106.     - The popup's title is allocated as a handle in the heap
  107.     - The popup is allocated as a handle in the heap
  108.     - Attribute values are only updated if nescessary
  109.     - The function PopupDraw first draws the popup to an offscreen bitmap
  110.     and then copies it to the popup's port. This eliminates flicker when
  111.     the popup is used as a CDEF, since the Control Manager sends a draw
  112.     message to a control whenever the control is clicked, even if no
  113.     drawing is actually needed.
  114.     
  115.     91/01/05 aih
  116.     - Inserted this standard header in all files
  117.  
  118.     90/12/15 Ari Halberstadt (aih)
  119.     - Created this file */
  120.  
  121. #include <Fonts.h>
  122. #include <GestaltEqu.h>
  123. #include <Icons.h>
  124. #include <LowMem.h>
  125. #include <Memory.h>
  126. #include <Palettes.h>
  127. #include <Resources.h>
  128. #include <Script.h>
  129. #include <TextEdit.h>
  130. #include <TextUtils.h>
  131. #include <ToolUtils.h>
  132. #include "PopupLib.h"
  133.  
  134. /*-------------------------------------------------------------------------*/
  135. /* conditional compilation */
  136. /*-------------------------------------------------------------------------*/
  137.  
  138. /*    Define CACHE_OFFSCREEN as 1 to enable the image of the popup menu
  139.     control to be cached offscreen for faster updates. If this is
  140.     defined as 0, then the control is reimaged every time PopupDraw
  141.     is called. We leave this disabled since it is difficult to detect
  142.     when the cached image needs to be updated. */
  143. #define CACHE_OFFSCREEN                (0)
  144.  
  145. #ifndef NDEBUG
  146.     
  147.     /* Define DRAW_POPUP as 1 to enable drawing of the various parts of the
  148.         the menu. You will normally want this to be enabled, but, while
  149.         debugging, you may want to see only the outlines of the parts of
  150.         the menu. To do so, you can set DRAW_POPUP to 0 and DRAW_RECTANGLES
  151.         to 1. */
  152.     #define DRAW_POPUP                (1)
  153.  
  154.     /* To reduce flicker the popup is normally first drawn to an off-screen
  155.         bitmap, then copied to the screen. To make debugging easier disable
  156.         offscreen drawing. Then you can step through each draw routine to
  157.         ensure that it is drawing correctly. */
  158.     #define DRAW_OFFSCREEN            (1)
  159.     
  160.     /* Define DRAW_RECTANGLES as 1 to enable framing of the various rectangles
  161.         of the menu. This is useful when debugging since it clearly displays the
  162.         rectangles, which otherwise can be tricky to calculate. */
  163.     #define DRAW_RECTANGLES            (0)
  164.  
  165.     /*    To test right-justified menus without using a right-justified
  166.         script system, define TEST_FLUSH_RIGHT as 1. */
  167.     #define TEST_FLUSH_RIGHT        (0)
  168.     
  169.     /* Another useful debugging trick is to define the following as something
  170.         greater than 1. This helps spot and fix off-by-one errors caused by
  171.         not taking into account the size of the frame and drop shadow. 
  172.         A value of at least 1/4" (18 pixels) provides good visual feedback
  173.         and allows using a ruler to get rough measurements of areas. */
  174.     #define kFrameSize                (1)    /* width of frame around popup */
  175.     #define kShadowSize                (1)    /* width of shadow around popup */
  176.     
  177. #else /* NDEBUG */
  178.  
  179.     #define DRAW_POPUP                (1)    /* if 1, draws the popup menu */
  180.     #define DRAW_OFFSCREEN            (1)    /* if 1, uses offscreen bitmap */
  181.     #define DRAW_RECTANGLES            (0)    /* if 1, draws frames around areas */
  182.     #define TEST_FLUSH_RIGHT        (0)    /* if 1, menus are always right justified */
  183.     #define kFrameSize                (1)    /* width of frame around popup */
  184.     #define kShadowSize                (1)    /* size of shadow around popup */
  185.     
  186. #endif /* NDEBUG */
  187.  
  188. /*-------------------------------------------------------------------------*/
  189. /* constants */
  190. /*-------------------------------------------------------------------------*/
  191.  
  192. #define kEllipses                        '…'    /* character appended to long strings */
  193. #define kShadowOffset                (3)    /* amount to offset shadow from frame */
  194. #define kTriangleWidth                (11)    /* width of the triangle */
  195. #define kTriangleHeight                (6)    /* height of the triangle */
  196. #define kIconMarginH                    (4)    /* horizontal margin around icon */
  197. #define kIconMarginV                    (2)    /* vertical margin around icon */
  198. #define kTriangleMargin                (5)    /* margin around triangle */
  199. #define kTitleMargin                    (5)    /* margin around title */
  200. #define kTitleMarginBottom            (1)    /* margin below title */
  201.  
  202. /* special values for the command key field of a menu item */
  203. enum {
  204.     kCmdHier = hMenuCmd,
  205.     kCmdScript,
  206.     kCmdIconReduced,
  207.     kCmdIconSmall,
  208.     kCmdReserved
  209. };
  210.  
  211. /*-------------------------------------------------------------------------*/
  212. /* types */
  213. /*-------------------------------------------------------------------------*/
  214.  
  215. /* used for translating a part code into a color part code */
  216. typedef struct {
  217.     short fore;
  218.     short back;
  219.     short disabled;
  220. } PopupPartTableType ;
  221.  
  222. /*-------------------------------------------------------------------------*/
  223. /* assertions */
  224. /*-------------------------------------------------------------------------*/
  225.  
  226. #ifndef NDEBUG
  227.  
  228.     #define require(x)            ((void) ((x) || PopupAssertFailed()))
  229.     #define ensure(x)                ((void) ((x) || PopupAssertFailed()))
  230.     #define check(x)                ((void) ((x) || PopupAssertFailed()))
  231.  
  232.     static int PopupAssertFailed(void)
  233.     {
  234.         DebugStr((StringPtr) "\pPopupAssertFailed");
  235.         return(0);
  236.     }
  237.  
  238.     static Boolean RectValid(const Rect *r)
  239.     {
  240.         if (r->top > r->bottom) return(false);
  241.         if (r->left > r->right) return(false);
  242.         return(true);
  243.     }
  244.     
  245.     static short RectWidth(const Rect *r)
  246.     {
  247.         return(r->right - r->left);
  248.     }
  249.     
  250.     static short RectHeight(const Rect *r)
  251.     {
  252.         return(r->bottom - r->top);
  253.     }
  254.     
  255.     static Boolean RectWithin(const Rect *r1, const Rect *r2)
  256.     {
  257.         return(    r1->left >= r2->left && r1->right <= r2->right &&
  258.                     r1->top >= r2->top && r1->bottom <= r2->bottom);
  259.     }
  260.     
  261. #else /* NDEBUG */
  262.  
  263.     #define require(x)            ((void) 0)
  264.     #define ensure(x)                ((void) 0)
  265.     #define check(x)                ((void) 0)
  266.     
  267. #endif /* NDEBUG */
  268.  
  269. /*-------------------------------------------------------------------------*/
  270. /* math utilities */
  271. /*-------------------------------------------------------------------------*/
  272.  
  273. /* return minimum of two signed long integers */
  274. static long lmin(long a, long b)
  275. {
  276.     return(a < b ? a : b);
  277. }
  278.  
  279. /* return maximum of two signed long integers */
  280. static long lmax(long a, long b)
  281. {
  282.     return(a > b ? a : b);
  283. }
  284.  
  285. /*-------------------------------------------------------------------------*/
  286. /* gestalt utilities */
  287. /*-------------------------------------------------------------------------*/
  288.  
  289. /* return the version of the Macintosh system software */
  290. static short MacVersion(void)
  291. {
  292.     OSErr err;
  293.     long response;
  294.     
  295.     err = Gestalt(gestaltSystemVersion, &response);
  296.     return(! err ? LoWord(response) : 0);
  297. }
  298.     
  299. /* return the version of QuickDraw */
  300. static short MacVersionQD(void)
  301. {
  302.     OSErr err;
  303.     long response;
  304.     
  305.     err = Gestalt(gestaltQuickdrawVersion, &response);
  306.     return(! err ? LoWord(response) : 0);
  307. }
  308.  
  309. /* true if the Macintosh supports color QuickDraw */
  310. static Boolean MacHasColorQD(void)
  311. {
  312.     return(MacVersionQD() >= gestalt8BitQD);
  313. }
  314.  
  315. /* true if the Macintosh supports offscreen graphics worlds */
  316. static Boolean MacHasGWorlds(void)
  317. {
  318.     /*    GWorlds are supported if (MacVersionQD() >= gestalt32BitQD) but
  319.         they behave sufficiently differently in systems before 7.0 that
  320.         it's difficult to get them to work with both pre-7.0 and post-7.0
  321.         systems, so we just support GWorlds in post-7.0 systems. */
  322.     return(MacVersion() >= 0x0700);
  323. }
  324.  
  325. /* true if the Macintosh has the icon utilities */
  326. static Boolean MacHasIconUtilities(void)
  327. {
  328.     OSErr err;
  329.     long response;
  330.     
  331.     // program_note: gestalt doesn't recognize the gestaltIconUtilitiesAttr,
  332.     // even though the icon utilities are available (this is with system 7 pro),
  333.     // so we just test for system 7.0
  334.     //err = Gestalt(gestaltIconUtilitiesAttr, &response);
  335.     //return(! err ? (response & (1<<gestaltIconUtilitiesPresent)) != 0 : false);
  336.     return(MacVersion() >= 0x0700);
  337. }
  338.  
  339. /*-------------------------------------------------------------------------*/
  340. /* graphics utilities */
  341. /*-------------------------------------------------------------------------*/
  342.  
  343. /* return the pixel depth of the pixel map */
  344. static short GetPixMapDepth(PixMapHandle pixmap)
  345. {
  346.     return((**pixmap).pixelSize);
  347. }
  348.  
  349. /* return the pixel depth of the port */
  350. static short GetPortDepth(CGrafPtr port)
  351. {
  352.     short depth;
  353.     
  354.     depth = 1;
  355.     if ((port->portVersion & 0xC000) == 0xC000)
  356.         depth = GetPixMapDepth(port->portPixMap);
  357.     return(depth);
  358. }
  359.  
  360. /* get the hilite color of the port */
  361. static void GetHiliteColor(CGrafPtr port, RGBColor *color)
  362. {
  363.     if ((port->portVersion & 0xC000) == 0xC000)
  364.         *color = (**(GVarHandle) (port->grafVars)).rgbHiliteColor;
  365.     else
  366.         LMGetHiliteRGB(color);
  367. }
  368.  
  369. /* return the pixel depth of the graphics device */
  370. static short GetDeviceDepth(GDHandle device)
  371. {
  372.     return(GetPixMapDepth((**device).gdPMap));
  373. }
  374.  
  375. /* Return a handle to the video device with the smallest pixel depth
  376.     that intersects the specified rectangle (given in global coordinates).
  377.     If no video device intersects the specified rectangle, then NULL is
  378.     returned. */
  379. static GDHandle GetMinDevice(const Rect *globalRect)
  380. {
  381.     GDHandle mindevice;
  382.     GDHandle curdevice;
  383.     Rect deviceRect;
  384.  
  385.     mindevice = NULL;
  386.     curdevice = GetDeviceList();
  387.     while (curdevice) {
  388.         if (TestDeviceAttribute(curdevice, screenDevice) &&
  389.              TestDeviceAttribute(curdevice, screenActive))
  390.         {
  391.             deviceRect = (**curdevice).gdRect;
  392.             if (SectRect(&deviceRect, globalRect, &deviceRect)) {
  393.                 if (! mindevice ||
  394.                      GetDeviceDepth(curdevice) <
  395.                      GetDeviceDepth(mindevice))
  396.                 {
  397.                     mindevice = curdevice;
  398.                 }
  399.             }
  400.         }
  401.         curdevice = GetNextDevice(curdevice);
  402.     }
  403.     return(mindevice);
  404. }
  405.  
  406. /* return the pixel map of the graphics world */
  407. static PixMapHandle GetPixMap(GWorldPtr gworld)
  408. {
  409.     return(MacVersion() >= 0x0700 ? GetGWorldPixMap(gworld) : gworld->portPixMap);
  410. }
  411.  
  412. /* get the text attributes of the current port */
  413. static void GetTextState(TextState *text)
  414. {
  415.     GrafPtr port;
  416.     
  417.     GetPort(&port);
  418.     text->font = port->txFont;
  419.     text->face = port->txFace;
  420.     text->mode = port->txMode;
  421.     text->size = port->txSize;
  422. }
  423.  
  424. /* set the text attributes of the current port */
  425. static void SetTextState(const TextState *text)
  426. {
  427.     TextFont(text->font);
  428.     TextFace(text->face);
  429.     TextMode(text->mode);
  430.     TextSize(text->size);
  431. }
  432.  
  433. /* set the text attributes of the current port to their defaults */
  434. static void TextNormal(void)
  435. {
  436.     TextFont(0);
  437.     TextFace(0);
  438.     TextSize(0);
  439.     TextMode(srcOr);
  440. }
  441.  
  442. /* get the color attributes of the current port */
  443. static void GetColorState(ColorState *color)
  444. {
  445.     if (MacHasColorQD()) {
  446.         GetForeColor(&color->fore);
  447.         GetBackColor(&color->back);
  448.     }
  449. }
  450.  
  451. /* set the color attributes of the current port */
  452. static void SetColorState(const ColorState *color)
  453. {
  454.     if (MacHasColorQD()) {
  455.         RGBForeColor(&color->fore);
  456.         RGBBackColor(&color->back);
  457.     }
  458. }
  459.  
  460. /*-------------------------------------------------------------------------*/
  461. /* rectangle utilities */
  462. /*-------------------------------------------------------------------------*/
  463.  
  464. /*    Convert the rectangle to global coordinates. */
  465. static void RectLocalToGlobal(Rect *r)
  466. {
  467.     Point origin = { 0, 0 };
  468.     
  469.     LocalToGlobal(&origin);
  470.     OffsetRect(r, origin.h, origin.v);
  471. }
  472.  
  473. /* Flip the rectangle 'flip' horizontally (mirror image) relative to
  474.     the rectangle 'within'. */    
  475. static void RectFlip(Rect *flip, const Rect *within)
  476. {
  477.     short offset;
  478.     
  479.     require(RectValid(flip));
  480.     require(RectValid(within));
  481.     require(within->left <= flip->left && flip->right <= within->right);
  482.     offset = (within->right - flip->right) - (flip->left - within->left);
  483.     flip->left += offset;
  484.     flip->right += offset;
  485. }
  486.  
  487. /* paint over the rectangle with the gray pattern */
  488. static void RectDisable(const Rect *r)
  489. {
  490.     PenState pen;
  491.     Pattern pat;
  492.  
  493.     require(RectValid(r));
  494.     GetPenState(&pen);
  495.     PenMode(patBic);
  496.     GetIndPattern(&pat, sysPatListID, 4);
  497.     PenPat(&pat);
  498.     PaintRect(r);
  499.     SetPenState(&pen);
  500. }
  501.  
  502. /*-------------------------------------------------------------------------*/
  503. /* icon utilities */
  504. /*-------------------------------------------------------------------------*/
  505.  
  506. /*    PlotSICN draws a small black and white icon from a resource of type
  507.     'SICN' that defines a 16 by 16 bit image, in the specified rectangle.
  508.     This is similar to the ToolBox routine PlotIcon. */
  509. static void PlotSICN(const Rect *bounds, Handle sicn)
  510. {
  511.     SignedByte state;
  512.     BitMap sicnBits;
  513.     GrafPtr port;
  514.     
  515.     state = HGetState(sicn);
  516.     MoveHHi(sicn);
  517.     HLock(sicn);
  518.     GetPort(&port);
  519.     sicnBits.rowBytes = 2;
  520.     sicnBits.baseAddr = *sicn;
  521.     sicnBits.bounds = *bounds;
  522.     CopyBits(&sicnBits, &port->portBits, bounds, bounds, srcCopy, NULL);
  523.     HSetState(sicn, state);
  524. }
  525.  
  526. /*-------------------------------------------------------------------------*/
  527. /* string utilities */
  528. /*-------------------------------------------------------------------------*/
  529.  
  530. /* fit string to width by truncating it and adding an extra character,
  531.     return width of string */
  532. static short TruncatePString(Str255 str, short maxwidth)
  533. {
  534.     const char extra = kEllipses;
  535.     short extrawidth;
  536.     short width;
  537.  
  538.     require(maxwidth >= 0);
  539.     if (MacVersion() >= 0x0700) {
  540.         (void) TruncString(maxwidth, str, truncEnd);
  541.         width = StringWidth(str);
  542.     }
  543.     else {
  544.         width = StringWidth(str);
  545.         if (width > maxwidth) {
  546.             if (maxwidth == 0) {
  547.                 /* optimization */
  548.                 width = 0;
  549.                 *str = 0;
  550.             }
  551.             else {
  552.                 /* truncate one character (or multi-byte character)
  553.                     at a time from the end of the string */
  554.                 while (*str > 0) {
  555.                     str[*str] = extra;
  556.                     width = StringWidth(str);
  557.                     if (width <= maxwidth)
  558.                         break;
  559.                     while (CharByte((Ptr) str, *str) > 0) {
  560.                         check(*str > 1);
  561.                         --*str;
  562.                     }
  563.                     check(*str > 0);
  564.                     --*str;
  565.                 }
  566.             }
  567.         }
  568.     }
  569.     ensure(width <= maxwidth);
  570.     ensure(width == StringWidth(str));
  571.     return(width);
  572. }
  573.  
  574. /*-------------------------------------------------------------------------*/
  575. /* menu utilities */
  576. /*-------------------------------------------------------------------------*/
  577.  
  578. /* true if the menu item is enabled */
  579. static Boolean MenuItemEnabled(MenuHandle menu, short item)
  580. {
  581.     unsigned long flags;
  582.     
  583.     require(0 <= item && item <= CountMItems(menu));
  584.     flags = (**menu).enableFlags;
  585.     return((flags & 1) != 0 && (item > 31 || (flags & (1UL << item)) != 0));
  586. }
  587.  
  588. /* get info about the icon for the specified menu item */
  589. static void MenuItemIcon(MenuHandle menu, short item,
  590.     ResType *iconType, short *iconID, Point *iconSize)
  591. {
  592.     short itemCmd;
  593.     short itemIcon;
  594.     Rect iconRect;
  595.     Handle iconRsrc;
  596.     
  597.     *iconID = 0;
  598.     *iconType = 0;
  599.     iconSize->h = iconSize->v = 0;
  600.     if (item > 0) {
  601.         GetItemCmd(menu, item, &itemCmd);
  602.         GetItemIcon(menu, item, &itemIcon);
  603.         if (itemIcon && itemCmd != kCmdScript) {
  604.             *iconID = itemIcon + 256;
  605.             if (itemCmd == kCmdIconReduced)
  606.                 iconSize->h = iconSize->v = 16;
  607.             if (MacHasColorQD() && GetResource('cicn', *iconID)) {
  608.                 *iconType = 'cicn';
  609.                 iconRsrc = GetResource('cicn', *iconID);
  610.                 if (iconRsrc && *iconRsrc && itemCmd != kCmdIconReduced) {
  611.                     /* get icon's size from its pixmap */
  612.                     iconRect = (**(CIconHandle) iconRsrc).iconPMap.bounds;
  613.                     iconSize->h = iconRect.right - iconRect.left;
  614.                     iconSize->v = iconRect.bottom - iconRect.top;
  615.                 }
  616.             }
  617.             else if (itemCmd == kCmdIconSmall) {
  618.                 *iconType = 'SICN';
  619.                 iconSize->h = iconSize->v = 16;
  620.             }
  621.             else {
  622.                 *iconType = 'ICON';
  623.                 if (itemCmd != kCmdIconReduced)
  624.                     iconSize->h = iconSize->v = 32;
  625.             }
  626.         }
  627.     }
  628. }
  629.  
  630. /* If the menu item has a color associated with it, then sets the
  631.     'foreColor' and 'backColor' parameters to the menu item's colors
  632.     and returns true. Otherwise, the 'foreColor' and 'backColor'
  633.     parameters are not changed and the function returns false. */
  634. static Boolean MenuItemColor(MenuHandle menu, short item,
  635.     RGBColor *foreColor, RGBColor *backColor)
  636. {
  637.     MCEntryPtr mcentry;    /* pointer to menu item's color entry */
  638.     Boolean color;            /* true if the item has a color */
  639.     
  640.     color = false;
  641.     mcentry = GetMCEntry((**menu).menuID, item);
  642.     if (item) {
  643.         if (mcentry) {
  644.             /* use item's color entry */
  645.             if (foreColor) *foreColor = mcentry->mctRGB2;
  646.             if (backColor) *backColor = mcentry->mctRGB4;
  647.             color = true;
  648.         }
  649.         else {
  650.             /* use default color entry */
  651.             mcentry = GetMCEntry((**menu).menuID, 0);
  652.             if (mcentry) {
  653.                 if (foreColor) *foreColor = mcentry->mctRGB3;
  654.                 if (backColor) *backColor = mcentry->mctRGB4;
  655.                 color = true;
  656.             }
  657.         }
  658.     }
  659.     else if (mcentry) {
  660.         /* use title's color entry */
  661.         if (foreColor) *foreColor = mcentry->mctRGB1;
  662.         if (backColor) *backColor = mcentry->mctRGB4;
  663.         color = true;
  664.     }
  665.     return(color);
  666. }
  667.  
  668. /*    this is similar to MenuItemColor, but returns the colors from the
  669.     color table for the menu bar */
  670. static Boolean MenuItemColorDefault(short item,
  671.     RGBColor *foreColor, RGBColor *backColor)
  672. {
  673.     MCEntryPtr mcentry;    /* pointer to menu item's color entry */
  674.     Boolean color;            /* true if the item has a color */
  675.     
  676.     color = false;
  677.     mcentry = GetMCEntry(0, 0);
  678.     if (mcentry) {
  679.         color = true;
  680.         if (item > 0) {
  681.             /* use item entry */
  682.             if (foreColor) *foreColor = mcentry->mctRGB3;
  683.             if (backColor) *backColor = mcentry->mctRGB2;
  684.         }
  685.         else {
  686.             /* use title entry */
  687.             if (foreColor) *foreColor = mcentry->mctRGB1;
  688.             if (backColor) *backColor = mcentry->mctRGB4;
  689.         }
  690.     }
  691.     return(color);
  692. }
  693.  
  694. /*-------------------------------------------------------------------------*/
  695. /* control utilities */
  696. /*-------------------------------------------------------------------------*/
  697.  
  698. /*    Return the control's color table, or NULL if it doesn't have
  699.     a color table. */
  700. static CCTabHandle ControlColorTable(ControlHandle ctl)
  701. {
  702.     AuxCtlHandle aux;
  703.     
  704.     return(GetAuxiliaryControlRecord(ctl, &aux) ? (**aux).acCTable : NULL);
  705. }
  706.  
  707. /* If the part has an entry in the control's color table, then
  708.     sets the 'color' parameter to the color specified in the table
  709.     and returns true, otherwise the 'color' parameter is not changed
  710.     and the function returns false. */
  711. static Boolean ControlCTPartColor(CCTabHandle cct, short part,
  712.     RGBColor *color)
  713. {
  714.     Boolean found;
  715.     short i;
  716.     
  717.     found = false;
  718.     if (cct) {
  719.         for (i = 0; i <= (**cct).ctSize && ! found; i++) {
  720.             if ((**cct).ctTable[i].value == part) {
  721.                 if (color)
  722.                     *color = (**cct).ctTable[i].rgb;
  723.                 found = true;
  724.             }
  725.         }
  726.     }
  727.     return(found);
  728. }
  729.  
  730. /*-------------------------------------------------------------------------*/
  731. /* popup menu routines */
  732. /*-------------------------------------------------------------------------*/
  733.  
  734. /* true if the popup is valid */
  735. Boolean PopupValid(PopupHandle popup)
  736. {
  737.     return(popup && (**popup).version == kPopupVersion);
  738. }
  739.  
  740. /*-------------------------------------------------------------------------*/
  741. /* port setup and restore */
  742. /*-------------------------------------------------------------------------*/
  743.  
  744. /* Return the alignment for the font in which the popup menu is drawn. */
  745. static short PopupJust(PopupHandle popup)
  746. {
  747.     GrafPtr savePort;
  748.     short just;
  749.     
  750.     if ((**popup).attr.wfont) {
  751.         GetPort(&savePort);
  752.         SetPort((**popup).port);
  753.         just = GetScript(FontScript(), smScriptJust);
  754.         SetPort(savePort);
  755.     }
  756.     else
  757.         just = GetSysDirection();
  758.     return(just);
  759. }
  760.  
  761. /* The menu definition function gets the font size in which to draw the
  762.     popup menu from the font size of the window manager port. So, to
  763.     set the system font, in addition to setting a low-memory global,
  764.     we also have to set the font size for the window manager port. */
  765. static void SetSysFontSize(short size)
  766. {
  767.     GrafPtr svport;
  768.     GrafPtr wmgrPort;
  769.     
  770.     GetPort(&svport);
  771.     GetWMgrPort(&wmgrPort);
  772.     SetPort(wmgrPort);
  773.     TextSize(size);
  774.     SetPort(svport);
  775. }
  776.  
  777. /* Returns the pixel depth of the port or device to which the popup is
  778.     being drawn. */
  779. static short PopupPixelDepth(PopupHandle popup)
  780. {
  781.     short depth;
  782.     
  783.     depth = GetPortDepth((CGrafPtr) (**popup).port);
  784.     if ((**popup).draw.gdepth)
  785.         depth = lmin(depth, (**popup).draw.gdepth);
  786.     return(depth);
  787. }
  788.  
  789. /* Remember the current drawing environment of the system font and size.
  790.     Must be balanced with a call to PopupPortRestoreSystem. */
  791. static void PopupPortSetupSystem(PopupHandle popup)
  792. {
  793.     /* If using the window's font, then set the system font and size
  794.         to the font and size of the popup's port. */
  795.     if ((**popup).attr.wfont) {
  796.         (**popup).draw.save.sysfont = LMGetSysFontFam();
  797.         (**popup).draw.save.syssize = LMGetSysFontSize();
  798.         LMSetSysFontFam((**popup).port->txFont);
  799.         LMSetSysFontSize((**popup).port->txSize);
  800.         SetSysFontSize((**popup).port->txSize);
  801.     }
  802. }
  803.  
  804. /* Restore the current drawing environment of the system font and size. */
  805. static void PopupPortRestoreSystem(PopupHandle popup)
  806. {
  807.     if ((**popup).attr.wfont) {
  808.         LMSetSysFontFam((**popup).draw.save.sysfont);
  809.         LMSetSysFontSize((**popup).draw.save.syssize);    
  810.         SetSysFontSize((**popup).draw.save.syssize);
  811.     }
  812. }
  813.  
  814. /* Remember the current drawing environment and set the drawing environment 
  815.     to that needed by the popup menu. Must be balanced with a call
  816.     to PopupPortRestore. */
  817. static void PopupPortSetup(PopupHandle popup)
  818. {
  819.     GrafPtr port;
  820.     PenState pen;
  821.     TextState text;
  822.     ColorState color;
  823.     
  824.     require(! (**popup).state.drawset);    
  825.     
  826.     #if TEST_FLUSH_RIGHT
  827.         SetSysDirection(teFlushRight);
  828.     #endif
  829.  
  830.     /* remember current port */
  831.     GetPort(&port);
  832.     (**popup).draw.save.port = port;
  833.  
  834.     /* save settings for popup's port */
  835.     SetPort((**popup).port);
  836.     GetPenState(&pen);
  837.     GetTextState(&text);
  838.     GetColorState(&color);
  839.     (**popup).draw.save.pen = pen;
  840.     (**popup).draw.save.text = text;
  841.     (**popup).draw.save.color = color;
  842.     if ((**popup).draw.gworld && port == (GrafPtr) (**popup).draw.gworld)
  843.         SetGWorld((**popup).draw.gworld, NULL);
  844.     
  845.     /* reset text and pen settings to default system settings */
  846.     TextNormal();
  847.     PenNormal();
  848.     
  849.     /* if using the window's font, use font and font size of popup's port */
  850.     if ((**popup).attr.wfont) {
  851.         TextFont(text.font);
  852.         TextSize(text.size);
  853.     }
  854.     
  855.     /* save clip region and clip to the popup's maximum bounding rectangle */
  856.     if ((**popup).draw.save.clip) {
  857.         Rect maxbounds = (**popup).r.maxbounds;
  858.         GetClip((**popup).draw.save.clip);
  859.         ClipRect(&maxbounds);
  860.     }
  861.         
  862.     (**popup).state.drawset = true;
  863.     ensure((**popup).state.drawset);
  864. }
  865.  
  866. /* Restore the drawing environment to its original state. */
  867. static void PopupPortRestore(PopupHandle popup)
  868. {
  869.     GrafPtr port;
  870.     PenState pen;
  871.     TextState text;
  872.     ColorState color;
  873.     GrafPtr wmgrPort;
  874.     TextState wmgrText;
  875.  
  876.     #if TEST_FLUSH_RIGHT
  877.         SetSysDirection(0);
  878.     #endif
  879.  
  880.     require((**popup).state.drawset);
  881.     port = (**popup).draw.save.port;
  882.     pen = (**popup).draw.save.pen;
  883.     text = (**popup).draw.save.text;
  884.     color = (**popup).draw.save.color;
  885.     if ((**popup).draw.save.clip) {
  886.         SetClip((**popup).draw.save.clip);
  887.         SetEmptyRgn((**popup).draw.save.clip);
  888.     }
  889.     SetColorState(&color);
  890.     SetTextState(&text);
  891.     SetPenState(&pen);
  892.     SetPort(port);    
  893.     (**popup).state.drawset = false;    
  894.     ensure(! (**popup).state.drawset);
  895. }
  896.  
  897. /* structure for saving the drawing environment when drawing the current
  898.     selection */
  899. typedef struct {
  900.     TextState textState;        /* saved text style */
  901.     ColorState colorState;    /* saved color state */
  902. } PopupDrawSelectionStructure;
  903.  
  904. /*    Setup the drawing environment for drawing the current selection string. */
  905. static void PopupDrawSelectionSetup(PopupHandle popup,
  906.     PopupDrawSelectionStructure *save)
  907. {
  908.     Rect selection;            /* current selection rectangle */
  909.     Style itemStyle;            /* menu item's style */
  910.     short itemCmd;                /* item's command character */
  911.     short itemScript;            /* item's script code */
  912.     long itemFondSize;        /* item's font ID and size */
  913.  
  914.     /* save the text and color states */
  915.     GetTextState(&save->textState);
  916.     GetColorState(&save->colorState);
  917.     
  918.     /* set the font script of the menu item */
  919.     if (! (**popup).attr.wfont) {
  920.         GetItemCmd((**popup).menu, (**popup).state.current, &itemCmd);
  921.         if (itemCmd == kCmdScript) {
  922.             GetItemIcon((**popup).menu, (**popup).state.current, &itemScript);
  923.             if (GetScript(itemScript, smScriptEnabled)) {
  924.                 itemFondSize = GetScript(itemScript, smScriptSysFondSize);
  925.                 TextFont(HiWord(itemFondSize));
  926.                 TextSize(LoWord(itemFondSize));
  927.             }
  928.         }
  929.     }
  930.     
  931.     /* set the text style of the menu item */
  932.     GetItemStyle((**popup).menu, (**popup).state.current, &itemStyle);
  933.     TextFace(itemStyle);
  934. }
  935.  
  936. /* restore the drawing environment after drawing the current selection */
  937. static void PopupDrawSelectionRestore(PopupHandle popup,
  938.     const PopupDrawSelectionStructure *save)
  939. {
  940.     SetTextState(&save->textState);
  941.     SetColorState(&save->colorState);
  942. }
  943.  
  944. /*-------------------------------------------------------------------------*/
  945. /* rectangle calculation routines */
  946. /*-------------------------------------------------------------------------*/
  947.  
  948. /* set popup's rectangles and erase old popup if the frame has changed */
  949. static PopupRectanglesSet(PopupHandle popup, const PopupRectanglesType *r)
  950. {
  951.     Rect bounds;
  952.     RgnHandle eraseRgn;
  953.     
  954.     bounds = (**popup).r.bounds;
  955.     if (! EqualRect(&bounds, &r->bounds)) {
  956.         eraseRgn = NewRgn();
  957.         if (eraseRgn && (**popup).draw.utilRgn) {
  958.             /* to reduce flicker only erase the
  959.                 area that doesn't overlap */
  960.             check(EmptyRgn((**popup).draw.utilRgn));
  961.             RectRgn(eraseRgn, &bounds);
  962.             RectRgn((**popup).draw.utilRgn, &r->bounds);
  963.             DiffRgn(eraseRgn, (**popup).draw.utilRgn, eraseRgn);
  964.             EraseRgn(eraseRgn);
  965.             DisposeRgn(eraseRgn);
  966.             SetEmptyRgn((**popup).draw.utilRgn);
  967.         }
  968.         else
  969.             EraseRect(&bounds);
  970.     }
  971.     (**popup).r = *r;
  972. }
  973.  
  974. /* calculate the popup's rectangles */
  975. void PopupCalculate(PopupHandle popup)
  976. {
  977.     struct {                                /* margins around some items */
  978.         struct { short left, right; } selection;
  979.         struct { short left, right; } title;
  980.         struct { short left, right; } triangle;
  981.     } margin = {
  982.         { kFrameSize, 0 },            /* selection */
  983.         { kTitleMargin, kTitleMargin },    /* title */
  984.         { 0, kTriangleMargin },            /* triangle */
  985.     };
  986.     struct {                                /* minimum widths of each item */
  987.         short content;
  988.         short hilite;
  989.         short title;
  990.         short triangle;
  991.         short selection;
  992.     } minwidth;
  993.     short lineHeight;                    /* height of a line */
  994.     short menuHeight;                    /* height of the selected menu item */
  995.     short menuWidth;                    /* width of the menu */
  996.     short maxwidth;                    /* for calculating maximum widths of items */
  997.     short width;                        /* for calculating widths of items */
  998.     Point iconSize;                    /* size of current item's icon */
  999.     ResType iconType;                    /* resource type of current item's icon */
  1000.     short iconID;                        /* resource ID  of current item's icon */
  1001.     FontInfo font;                        /* info about the current font size */
  1002.     Str255 title;                        /* the popup's title */
  1003.     Rect content;                        /* rectangle enclosing content (excludes frame) */
  1004.     PopupRectanglesType r;            /* the calculated rectangles */
  1005.     PopupDrawSelectionStructure saveState; /* saved state for calculating menu height */
  1006.     GrafPtr port;                        /* the current port being drawn into */
  1007.     
  1008.     /* don't calculate if drawing is turned off */
  1009.     if (! (**popup).attr.draw) return;
  1010.     
  1011.     /* initialize settings */
  1012.     PopupPortSetup(popup);
  1013.     PopupTitle(popup, title);
  1014.     GetPort(&port);
  1015.     
  1016.     /* The following calls to CalcMenuSize and GetFontInfo have a bizarre
  1017.         requirement. When using a window font other than the system font
  1018.         (i.e., using the popupUseWFont variation code) the first call to
  1019.         CalcMenuSize would calculate the menu's size using the standard
  1020.         system font, instead of the font that the CDEF specified. The
  1021.         second call to CalcMenuSize calculates the menu's size using
  1022.         the correct font size. Similarly, if GetFontInfo were called before
  1023.         CalcMenuSize were called, GetFontInfo would return information about
  1024.         the wrong font. This seems either like a bug in the OS (both systems
  1025.         6.0.5 and 7.0), or an attempt by Apple to work around some other
  1026.         bug in the OS. It took me a long time to figure out a work-around
  1027.         for this problem. */
  1028.         
  1029.     /* get width of menu, including the width of the triangle */
  1030.     PopupPortSetupSystem(popup);
  1031.     CalcMenuSize((**popup).menu);
  1032.     CalcMenuSize((**popup).menu);
  1033.     PopupPortRestoreSystem(popup);
  1034.     menuWidth = (**(**popup).menu).menuWidth + kTriangleWidth + kTriangleMargin;
  1035.  
  1036.     /* setup port for current selection string */
  1037.     PopupDrawSelectionSetup(popup, &saveState);
  1038.  
  1039.     /* get information about the font in which the current selection
  1040.         will be drawn */
  1041.     GetFontInfo(&font);
  1042.     lineHeight = font.ascent + font.descent + font.leading;
  1043.     
  1044.     /* calculate height of current selection */
  1045.     menuHeight = 0;
  1046.     MenuItemIcon((**popup).menu, (**popup).state.current,
  1047.         &iconType, &iconID, &iconSize);
  1048.     if (iconSize.v > 0)
  1049.         menuHeight = iconSize.v + kIconMarginV;
  1050.     
  1051.     /* the height must be at least as large as the minimum height
  1052.         of a line and the minimum height needed to display the down
  1053.         triangle */
  1054.     if (menuHeight < lineHeight)
  1055.         menuHeight = lineHeight;
  1056.     if (menuHeight < kTriangleHeight + 4)
  1057.         menuHeight = kTriangleHeight + 4;
  1058.         
  1059.     /* restore port */
  1060.     PopupDrawSelectionRestore(popup, &saveState);
  1061.  
  1062.     /* get information about the font in which the title will be drawn */
  1063.     GetFontInfo(&font);
  1064.     lineHeight = font.ascent + font.descent + font.leading;
  1065.     
  1066.     /* adjust margins */
  1067.     
  1068.     if (! *title || (**popup).attr.typein) {
  1069.         /* Type-in popups don't display the title or the current selection,
  1070.             so their margins aren't needed. If the title is empty then the
  1071.             title's margins can also be empty. */
  1072.         if ((**popup).attr.typein) {
  1073.             margin.triangle.left = 3;
  1074.             margin.triangle.right = 3;
  1075.             menuHeight = lineHeight;
  1076.             font.widMax = 0;
  1077.         }
  1078.         margin.title.left = 0;
  1079.         margin.title.right = 0;
  1080.     }
  1081.     
  1082.     /* for right justified popups we need a little extra space for the drop
  1083.         shadow between the current selection area and the title */
  1084.     if ((**popup).attr.just == teFlushRight)
  1085.         margin.selection.left += kShadowSize;
  1086.         
  1087.     /* calculate minimum widths */
  1088.     
  1089.     minwidth.triangle = kTriangleWidth;
  1090.     minwidth.title = (*title ? font.widMax : 0);
  1091.     minwidth.hilite = minwidth.title + margin.title.left + margin.title.right;
  1092.     minwidth.selection = font.widMax + minwidth.triangle + margin.triangle.left + margin.triangle.right;
  1093.     minwidth.content = minwidth.hilite + minwidth.selection +
  1094.                             margin.selection.left + margin.selection.right;
  1095.     
  1096.     /* calculate rectangles */
  1097.     
  1098.     /* copy maxbounds rectangle */
  1099.     r.maxbounds = (**popup).r.maxbounds;
  1100.     
  1101.     /* calculate content rectangle; all the other rectangles will be
  1102.         calculated relative to this rectangle */
  1103.     content.top = r.maxbounds.top + kFrameSize;
  1104.     content.left = r.maxbounds.left + kFrameSize;
  1105.     content.bottom = content.top + menuHeight;
  1106.     content.right = content.left +
  1107.         lmax(r.maxbounds.right - r.maxbounds.left - 2 * kFrameSize - kShadowSize,
  1108.              minwidth.content);
  1109.  
  1110.     /* vertically center content in maxbounds */
  1111.     {    short offset = ((r.maxbounds.bottom - r.maxbounds.top) -
  1112.                                (content.bottom - content.top)) / 2;
  1113.         OffsetRect(&content, 0, lmax(0, offset - kShadowSize));
  1114.     }
  1115.     
  1116.     /* calculate hilite rectangle */
  1117.     r.hilite = content;
  1118.     r.hilite.left = content.left;
  1119.     r.hilite.right = content.right - minwidth.selection;
  1120.  
  1121.     /* calculate title rectangle */
  1122.     check(content.bottom - content.top >= lineHeight);
  1123.     r.title.top = content.top + (content.bottom - content.top - lineHeight) / 2;
  1124.     r.title.left = content.left + margin.title.left;
  1125.     r.title.bottom = r.title.top + lineHeight - kTitleMarginBottom;
  1126.     /* calculate width of title rectangle */
  1127.     if ((**popup).attr.typein || ! *title)
  1128.         width = 0; /* don't show title */
  1129.     else {
  1130.         /* popup has a title */
  1131.         maxwidth =
  1132.             r.hilite.right - r.hilite.left -
  1133.             margin.title.left - margin.title.right;
  1134.         check(maxwidth >= minwidth.title);
  1135.         if ((**popup).attr.title.width) {
  1136.             /* use fixed title width as specified by application */
  1137.             width = lmin(maxwidth, (**popup).attr.title.width);
  1138.             width = lmax(width, minwidth.title);
  1139.         }
  1140.         else {
  1141.             /* use actual width of title string */
  1142.             Style style = port->txFace;
  1143.             TextFace((**popup).attr.title.style);
  1144.             width = TruncatePString(title, maxwidth);
  1145.             TextFace(style);
  1146.         }
  1147.         check(width <= maxwidth);
  1148.     }
  1149.     check(width >= minwidth.title);
  1150.     r.title.right = r.title.left + width;
  1151.  
  1152.     /* adjust right edge of hilite rectangle now that width of title is known */
  1153.     r.hilite.right = r.title.right + margin.title.right;
  1154.     
  1155.     /* calculate selection rectangle, initially using the maximum possible width */
  1156.     r.selection = content;
  1157.     r.selection.left = r.hilite.right + margin.selection.left;
  1158.     r.selection.right = content.right - margin.selection.right;
  1159.  
  1160.     /* calculate width of current selection */
  1161.     if ((**popup).attr.typein)
  1162.         width = minwidth.selection;
  1163.     else if ((**popup).attr.fixedwidth)
  1164.         width = lmax(r.selection.right - r.selection.left, minwidth.selection);
  1165.     else {
  1166.         maxwidth = r.selection.right - r.selection.left;
  1167.         check(maxwidth >= minwidth.selection);
  1168.         width = lmin(maxwidth, menuWidth);
  1169.         width = lmax(width, minwidth.selection);
  1170.     }
  1171.  
  1172.     /* adjust right edge now that we know how wide everything is */
  1173.     r.selection.right = r.selection.left + width;
  1174.     content.right = r.selection.right + margin.selection.right;
  1175.     
  1176.     /* calculate triangle rectangle--center triangle vertically at
  1177.         right edge of selection rectangle */
  1178.     check(content.bottom - content.top >= kTriangleHeight);
  1179.     r.triangle.top = content.top + (content.bottom - content.top - kTriangleHeight) / 2;
  1180.     r.triangle.left = r.selection.right - kTriangleWidth - margin.triangle.right;
  1181.     r.triangle.bottom = r.triangle.top + kTriangleHeight;
  1182.     r.triangle.right = r.triangle.left + kTriangleWidth;
  1183.  
  1184.     /* calculate frame rectangle--surrounds content area, and includes the
  1185.         frame and shadow */
  1186.     r.bounds.top = content.top - kFrameSize;
  1187.     r.bounds.left = content.left - kFrameSize;
  1188.     r.bounds.right = content.right + kFrameSize;
  1189.     r.bounds.bottom = content.bottom + kFrameSize + kShadowSize;
  1190.     if ((**popup).attr.just != teFlushRight)
  1191.         r.bounds.right += kShadowSize; /* in teFlushRight shadow is part of content */
  1192.     
  1193.     /* mirror image rectangles if using a right justified menu */
  1194.     if ((**popup).attr.just == teFlushRight) {
  1195.         Rect maxbounds;
  1196.         
  1197.         /* copy and adjust maxbounds so flipping will work */
  1198.         maxbounds.top = r.maxbounds.top;
  1199.         maxbounds.left = r.maxbounds.left;
  1200.         maxbounds.bottom = r.maxbounds.top +
  1201.             lmax(r.maxbounds.bottom - r.maxbounds.top,
  1202.                  r.bounds.bottom - r.bounds.top);
  1203.         maxbounds.right = r.maxbounds.left +
  1204.             lmax(r.maxbounds.right - r.maxbounds.left,
  1205.                  r.bounds.right - r.bounds.left);
  1206.  
  1207.         /* flip the rectangles */
  1208.         RectFlip(&r.bounds, &maxbounds);
  1209.         RectFlip(&r.hilite, &maxbounds);
  1210.         RectFlip(&r.selection, &maxbounds);
  1211.         RectFlip(&r.title, &maxbounds);        
  1212.         RectFlip(&r.triangle, &maxbounds);
  1213.         if (PopupJust(popup) != teFlushRight)
  1214.             RectFlip(&r.triangle, &r.selection);
  1215.     }
  1216.     
  1217.     /* make sure everything's ok */
  1218.     check(RectWidth(&r.hilite) >= minwidth.hilite);
  1219.     check(RectWidth(&r.title) >= minwidth.title);
  1220.     check(RectWidth(&r.selection) >= minwidth.selection);
  1221.     check(RectWidth(&r.triangle) >= minwidth.triangle);
  1222.     check(RectWithin(&r.hilite, &r.bounds));
  1223.     check(RectWithin(&r.title, &r.hilite));
  1224.     check(RectWithin(&r.selection, &r.bounds));
  1225.     check(RectWithin(&r.triangle, &r.selection));
  1226.     check(! RectWithin(&r.hilite, &r.selection));
  1227.     check(! RectWithin(&r.selection, &r.hilite));
  1228.  
  1229.     /* set the popup's rectangles and restore the environment */
  1230.     PopupRectanglesSet(popup, &r);
  1231.     PopupPortRestore(popup);
  1232. }
  1233.  
  1234. /*-------------------------------------------------------------------------*/
  1235. /* more drawing routines */
  1236. /*-------------------------------------------------------------------------*/
  1237.  
  1238. /* Inserts the menu into the hierarchical menu list if necessary.
  1239.     This also sets the menu's color table if there is a 'mctb'
  1240.     resource with the same ID as the menu. True is returned if the
  1241.     menu was inserted into the menu table, in which case you should
  1242.     call PopupDeleteMenu when finished using the menu. */
  1243. static Boolean PopupInsertMenu(PopupHandle popup)
  1244. {
  1245.     Boolean inserted;
  1246.     SignedByte state;
  1247.     Handle mctb;
  1248.     
  1249.     require(PopupValid(popup));
  1250.     inserted = false;
  1251.     if (! GetMenuHandle((**(**popup).menu).menuID)) {
  1252.         inserted = true;
  1253.         InsertMenu((**popup).menu, -1);
  1254.         if (MacHasColorQD()) {
  1255.             mctb = GetResource('mctb', (**(**popup).menu).menuID);
  1256.             if (mctb && *mctb) {
  1257.                 state = HGetState(mctb);
  1258.                 MoveHHi(mctb);
  1259.                 HLock(mctb);
  1260.                 SetMCEntries(*(short *) *mctb, (MCTablePtr) (*mctb + sizeof(short)));
  1261.                 HSetState(mctb, state);
  1262.             }
  1263.         }
  1264.     }
  1265.     return(inserted);
  1266. }
  1267.  
  1268. /* remove the menu from the menu list */
  1269. static void PopupDeleteMenu(PopupHandle popup, Boolean inserted)
  1270. {
  1271.     require(PopupValid(popup));
  1272.     if (inserted)
  1273.         DeleteMenu((**(**popup).menu).menuID);
  1274. }
  1275.  
  1276. /* Set the 'foreColor' and 'backColor' parameters to the foreground and
  1277.     background colors for the specified part of the control, and return
  1278.     true if should use color when drawing the part. The 'textMode'
  1279.     parameter is set to grayishTextOr if text should be grayed out,
  1280.     otherwise it is set to the text mode of the current port. The
  1281.     current port should be the port into which the popup is being
  1282.     drawn. */
  1283. static Boolean PopupPartColor(PopupHandle popup, short part,
  1284.     RGBColor *foreColor, RGBColor *backColor, short *textMode)
  1285. {
  1286.     const RGBColor whiteRGBColor = { 0, 0, 0 };
  1287.     const RGBColor blackRGBColor = { 65535, 65535, 65535 };
  1288.     const PopupPartTableType table[kPopupPartLast - kPopupPartFirst] = {
  1289.         { kPopupPartTitleFore, kPopupPartTitleBack, kPopupPartTitleDisabled, },
  1290.         { kPopupPartItemFore, kPopupPartItemBack, kPopupPartItemDisabled, },
  1291.         { kPopupPartIconFore, kPopupPartIconBack, kPopupPartIconDisabled, },
  1292.         { kPopupPartTriangleFore, kPopupPartTriangleBack, kPopupPartTriangleDisabled, },
  1293.         { kPopupPartFrameFore, kPopupPartFrameBack, kPopupPartFrameDisabled, },
  1294.         { kPopupPartShadowFore, kPopupPartShadowBack, kPopupPartShadowDisabled, },
  1295.     };
  1296.     const PopupPartTableType *entry;    /* entry in table */
  1297.     short    disabledTextMode;    /* mode for drawing disabled text */
  1298.     Boolean enabled;            /* true if the part is enabled */
  1299.     Boolean fore;                /* true if got a foreground color */
  1300.     Boolean back;                /* true if got a background color */
  1301.     Boolean color;                /* true if got a color */
  1302.     Boolean gray;                /* true if should select a gray color */
  1303.     GrafPtr port;                /* for getting current text mode */
  1304.     CCTabHandle cct;            /* control's color table */
  1305.     CCTabHandle dcct;            /* default control color table */
  1306.     short item;                    /* menu item to get color for */
  1307.     
  1308.     require((**popup).state.drawset);
  1309.     require(kPopupPartFirst <= part && part < kPopupPartLast);
  1310.     
  1311.     /* only use color if the destination port is a color port
  1312.         (otherwise, the colors would be mapped to black, which
  1313.         would be pretty ugly) */
  1314.     *textMode = (**popup).port->txMode;
  1315.     if (PopupPixelDepth(popup) > 1) {
  1316.         
  1317.         check(MacHasColorQD());
  1318.         
  1319.         /* get default colors */
  1320.         *foreColor = blackRGBColor;
  1321.         *backColor = whiteRGBColor;
  1322.         disabledTextMode = *textMode;
  1323.         gray = false;
  1324.         
  1325.         /* get control color tables */
  1326.         dcct = ControlColorTable(NULL);
  1327.         cct = ControlColorTable((**popup).ctl);
  1328.         if (cct == dcct)
  1329.             cct = NULL;
  1330.             
  1331.         /* find entry in part code table */
  1332.         entry = table + (part - kPopupPartFirst);
  1333.         
  1334.         /* determine if part is enabled */
  1335.         enabled = (**popup).attr.enabled;
  1336.         if (enabled) {
  1337.             switch (part) {
  1338.             case kPopupPartItem:
  1339.             case kPopupPartIcon:
  1340.                 enabled = MenuItemEnabled((**popup).menu, (**popup).state.current);
  1341.                 break;
  1342.             }
  1343.         }
  1344.         
  1345.         /* get color for part */
  1346.         switch (part) {
  1347.         case kPopupPartTitle:
  1348.         case kPopupPartItem:
  1349.         case kPopupPartIcon:
  1350.             switch (part) {
  1351.             case kPopupPartTitle:
  1352.                 disabledTextMode = grayishTextOr;
  1353.                 item = 0;
  1354.                 break;
  1355.             case kPopupPartItem:
  1356.                 disabledTextMode = grayishTextOr;
  1357.                 item = (**popup).state.current;
  1358.                 break;
  1359.             case kPopupPartIcon:
  1360.                 item = (**popup).state.current;
  1361.                 break;
  1362.             }
  1363.             color = MenuItemColor((**popup).menu, item, foreColor, backColor);
  1364.             if (! color) {
  1365.                 fore = ControlCTPartColor(cct, entry->fore, foreColor);
  1366.                 back = ControlCTPartColor(cct, entry->back, backColor);
  1367.                 if (! fore) fore = MenuItemColorDefault(item, foreColor, NULL);
  1368.                 if (! back) back = MenuItemColorDefault(item, NULL, backColor);
  1369.                 if (! fore) (void) ControlCTPartColor(dcct, cTextColor, foreColor);
  1370.                 if (! back) (void) ControlCTPartColor(dcct, cBodyColor, backColor);
  1371.             }
  1372.             if (! enabled) {
  1373.                   color = ControlCTPartColor(cct, entry->disabled, foreColor);
  1374.                   if (! color)
  1375.                       *textMode = disabledTextMode;
  1376.               }
  1377.             break;
  1378.         case kPopupPartTriangle:
  1379.         case kPopupPartFrame:
  1380.         case kPopupPartShadow:
  1381.             fore = ControlCTPartColor(cct, entry->fore, foreColor);
  1382.             back = ControlCTPartColor(cct, entry->back, backColor);
  1383.             switch (part) {
  1384.             case kPopupPartTriangle:
  1385.                 if (! fore) (void) ControlCTPartColor(dcct, cTextColor, foreColor);
  1386.                 if (! back) (void) ControlCTPartColor(dcct, cBodyColor, backColor);
  1387.                 break;
  1388.             case kPopupPartFrame:
  1389.                 if (! fore) (void) ControlCTPartColor(dcct, cFrameColor, foreColor);
  1390.                 break;
  1391.             case kPopupPartShadow:
  1392.                 if (! fore) (void) ControlCTPartColor(dcct, cFrameColor, foreColor);
  1393.                 break;
  1394.             }
  1395.             if (! enabled)
  1396.                   gray = ! ControlCTPartColor(cct, entry->disabled, foreColor);
  1397.             break;
  1398.         default:
  1399.             /* error */
  1400.             check(false);
  1401.         }
  1402.         
  1403.         /* derive a gray color from a blend of the foreground and background colors */
  1404.         if (gray) {
  1405.             if ((**popup).draw.gdevice)
  1406.                 (void) GetGray((**popup).draw.gdevice, backColor, foreColor);
  1407.             else
  1408.                 (void) GetGray(GetMainDevice(), backColor, foreColor);
  1409.         }
  1410.     }
  1411.     return(PopupPixelDepth(popup) > 1);
  1412. }
  1413.  
  1414. /*    Set the foreground and background colors to the colors appropriate for
  1415.     drawing the specified part of the control. */
  1416. static void PopupPartColorSet(PopupHandle popup, short part)
  1417. {
  1418.     RGBColor foreColor;
  1419.     RGBColor backColor;
  1420.     short mode;
  1421.     
  1422.     if (PopupPartColor(popup, part, &foreColor, &backColor, &mode)) {
  1423.         RGBForeColor(&foreColor);
  1424.         RGBBackColor(&backColor);
  1425.         TextMode(mode);
  1426.     }
  1427. }
  1428.  
  1429. /* draw the frame around the popup menu */
  1430. static void PopupDrawFrame(PopupHandle popup)
  1431. {
  1432.     Rect frame;
  1433.     PenState pen;
  1434.     ColorState color;
  1435.     
  1436.     require((**popup).state.drawset);
  1437.     
  1438.     /* save state */
  1439.     GetPenState(&pen);
  1440.     GetColorState(&color);
  1441.     
  1442.     /* draw frame */
  1443.     PopupPartColorSet(popup, kPopupPartFrame);
  1444.     frame = (**popup).r.bounds;    
  1445.     if ((**popup).attr.just == teFlushRight)
  1446.         frame.right = (**popup).r.selection.right + kFrameSize + kShadowSize;
  1447.     else
  1448.         frame.left = (**popup).r.selection.left - kFrameSize;
  1449.     frame.right -= kShadowSize;
  1450.     frame.bottom -= kShadowSize;
  1451.     PenSize(kFrameSize, kFrameSize);
  1452.     FrameRect(&frame);
  1453.  
  1454.     /* draw drop shadow */
  1455.     PopupPartColorSet(popup, kPopupPartShadow);
  1456.     PenSize(kShadowSize, kShadowSize);
  1457.     MoveTo(frame.right, frame.top + kShadowOffset);
  1458.     LineTo(frame.right, frame.bottom);
  1459.     LineTo(frame.left + kShadowOffset, frame.bottom);
  1460.     
  1461.     /* restore state */
  1462.     SetPenState(&pen);
  1463.     SetColorState(&color);
  1464. }
  1465.  
  1466. /* draw the triangle */
  1467. static void PopupDrawTriangle(PopupHandle popup)
  1468. {
  1469.     Rect triangle;    /* triangle's rectangle */
  1470.     short    height;    /* height of triangle */
  1471.     short    width;    /* width of current line */
  1472.     short    i;            /* index to lines of triangle */
  1473.     ColorState color;
  1474.     
  1475.     require((**popup).state.drawset);
  1476.     GetColorState(&color);
  1477.     PopupPartColorSet(popup, kPopupPartTriangle);
  1478.     triangle = (**popup).r.triangle;
  1479.     height = kTriangleHeight;
  1480.     width = kTriangleWidth - 1;
  1481.     for (i = 0; i < height; i++) {
  1482.         MoveTo(triangle.left + i, triangle.top + i);
  1483.         LineTo(triangle.left + i + width, triangle.top + i);
  1484.         width -= 2;
  1485.     }
  1486.     SetColorState(&color);
  1487. }
  1488.  
  1489. /* draw the string within the bounding rectangle; if the string is too wide,
  1490.     it is truncated and an ellipses character is appended */
  1491. static void PopupDrawString(PopupHandle popup, Str255 str, const Rect *bounds,
  1492.     short just)
  1493. {
  1494.     FontInfo    font;
  1495.     short width;
  1496.     
  1497.     require((**popup).state.drawset);
  1498.     require(RectValid(bounds));
  1499.     GetFontInfo(&font);
  1500.     width = TruncatePString(str, bounds->right - bounds->left);
  1501.     if (just == teFlushRight)
  1502.         MoveTo(bounds->right - width, bounds->bottom - font.descent);
  1503.     else
  1504.         MoveTo(bounds->left, bounds->bottom - font.descent);
  1505.     DrawString(str);
  1506. }
  1507.  
  1508. /* draw the current selection string and icon */
  1509. static void PopupDrawSelection(PopupHandle popup)
  1510. {
  1511.     PopupDrawSelectionStructure saveState; /* saved state */
  1512.     Str255 itemString;        /* string of currently selected menu item */
  1513.     FontInfo font;                /* information about the current font */
  1514.     Rect selectionRect;        /* rectangle to draw string in */
  1515.     short selectionHeight;    /* height of current selection rectangle */
  1516.     short lineHeight;            /* height of a line of text */
  1517.     Rect iconRect;                /* rectangle to draw icon in */
  1518.     Point iconSize;            /* size of current item's icon */
  1519.     ResType iconType;            /* resource type of current item's icon */
  1520.     short iconID;                /* resource ID of current item's icon */
  1521.     Handle iconHandle;        /* handle to current item's icon */
  1522.     SignedByte iconState;    /* state of handle to icon */
  1523.     Boolean iconDraw;            /* true if should draw the icon */
  1524.     IconTransformType iconTransform; /* transformation to apply when drawing icon */
  1525.     
  1526.     require((**popup).state.drawset);
  1527.     require(! (**popup).attr.typein);
  1528.     
  1529.     /* initially, set the rectangle within which to draw the string
  1530.         equal to the entire current selection rectangle */
  1531.     selectionRect = (**popup).r.selection;
  1532.     
  1533.     /* set up drawing environment */
  1534.     PopupDrawSelectionSetup(popup, &saveState);
  1535.     
  1536.     /* set the item's color */
  1537.     PopupPartColorSet(popup, kPopupPartItem);
  1538.     EraseRect(&selectionRect);
  1539.     
  1540.     /* calculate size of current item's icon */
  1541.     MenuItemIcon((**popup).menu, (**popup).state.current,
  1542.         &iconType, &iconID, &iconSize);
  1543.  
  1544.     /* exclude icon from selection rectangle */
  1545.     if (iconSize.h > 0) {
  1546.         if (PopupJust(popup) == teFlushRight)
  1547.             selectionRect.right -= iconSize.h + kIconMarginH;
  1548.         else
  1549.             selectionRect.left += iconSize.h + kIconMarginH;
  1550.     }
  1551.     
  1552.     /* exclude triangle from selection rectangle */
  1553.     if (PopupJust(popup) == teFlushRight)
  1554.         selectionRect.left += kTriangleWidth + kTriangleMargin;
  1555.     else
  1556.         selectionRect.right -= kTriangleWidth + kTriangleMargin;
  1557.     
  1558.     /* align the bounding box within which to draw the current selection
  1559.         string with the base line of the title string and with the edge
  1560.         of the current selection rectangle */
  1561.     GetFontInfo(&font);
  1562.     lineHeight = font.ascent + font.descent + font.leading;
  1563.     selectionHeight = selectionRect.bottom - selectionRect.top;
  1564.     check(selectionHeight >= lineHeight);
  1565.     selectionRect.top += (selectionHeight - lineHeight) / 2 - 1;
  1566.     selectionRect.bottom = selectionRect.top + lineHeight;
  1567.     if (PopupJust(popup) == teFlushRight)
  1568.         selectionRect.right -= font.widMax;
  1569.     else
  1570.         selectionRect.left += font.widMax;
  1571.  
  1572.     /* fix selection rectangle */
  1573.     if (selectionRect.right < selectionRect.left)
  1574.         selectionRect.right = selectionRect.left;
  1575.     if (selectionRect.left > selectionRect.right)
  1576.         selectionRect.left = selectionRect.right;
  1577.     check(RectValid(&selectionRect));
  1578.     
  1579.     /* draw the item string */
  1580.     GetMenuItemText((**popup).menu, (**popup).state.current, itemString);
  1581.     PopupDrawString(popup, itemString, &selectionRect, PopupJust(popup));
  1582.     
  1583.     /* calculate the icon's rectangle, but don't draw the icon if
  1584.         it overlaps the item's text or the triangle */
  1585.     iconDraw = true;
  1586.     iconRect = (**popup).r.selection;
  1587.     check(iconRect.bottom - iconRect.top >= iconSize.v);
  1588.     iconRect.top += (iconRect.bottom - iconRect.top - iconSize.v) / 2;
  1589.     iconRect.bottom = iconRect.top + iconSize.v;
  1590.     if (PopupJust(popup) == teFlushRight) {
  1591.         iconRect.right -= font.widMax;
  1592.         iconRect.left = iconRect.right - iconSize.h;
  1593.         if (iconRect.left < selectionRect.right ||
  1594.              iconRect.left < (**popup).r.triangle.right)
  1595.         {
  1596.             iconDraw = false;
  1597.         }
  1598.     }
  1599.     else {
  1600.         iconRect.left += font.widMax;
  1601.         iconRect.right = iconRect.left + iconSize.h;
  1602.         if (iconRect.right > selectionRect.left ||
  1603.              iconRect.right > (**popup).r.triangle.left)
  1604.         {
  1605.             iconDraw = false;
  1606.         }
  1607.     }
  1608.         
  1609.     /* determine transformation to apply when drawing icon */
  1610.     iconTransform = ttNone;
  1611.     if (! (**popup).attr.enabled ||
  1612.          ! MenuItemEnabled((**popup).menu, (**popup).state.current))
  1613.     {
  1614.         iconTransform = ttDisabled;
  1615.     }
  1616.  
  1617.     /* set the color in which to draw black and white icons */
  1618.     PopupPartColorSet(popup, kPopupPartIcon);
  1619.         
  1620.     /* draw the icon */
  1621.     if (iconDraw && iconType) {            
  1622.         switch (iconType) {
  1623.         case 'SICN':
  1624.             OffsetRect(&iconRect, 0, -1);
  1625.             iconHandle = GetResource(iconType, iconID);
  1626.             if (iconHandle && *iconHandle) {
  1627.                 iconState = HGetState(iconHandle);
  1628.                 HNoPurge(iconHandle);
  1629.                 PlotSICN(&iconRect, iconHandle);
  1630.                 HSetState(iconHandle, iconState);
  1631.             }
  1632.             break;
  1633.         case 'ICON':
  1634.             OffsetRect(&iconRect, -1, 0);
  1635.             iconHandle = GetResource(iconType, iconID);
  1636.             if (iconHandle && *iconHandle) {
  1637.                 iconState = HGetState(iconHandle);
  1638.                 HNoPurge(iconHandle);
  1639.                 PlotIcon(&iconRect, iconHandle);
  1640.                 HSetState(iconHandle, iconState);
  1641.             }
  1642.             break;
  1643.         case 'cicn':
  1644.             check(MacHasColorQD());
  1645.             iconHandle = (Handle) GetCIcon(iconID);
  1646.             if (iconHandle) {
  1647.                 if (*iconHandle) {
  1648.                     if (MacHasIconUtilities())
  1649.                         PlotCIconHandle(&iconRect, atNone, iconTransform, (CIconHandle) iconHandle);
  1650.                     else
  1651.                         PlotCIcon(&iconRect, (CIconHandle) iconHandle);
  1652.                 }
  1653.                 DisposeCIcon((CIconHandle) iconHandle);
  1654.             }
  1655.             break;
  1656.         }
  1657.     }
  1658.  
  1659.     /* restore drawing environment */
  1660.     PopupDrawSelectionRestore(popup, &saveState);
  1661.     
  1662.     /* gray over entire selection if item is disabled and we're drawing
  1663.         into a black and white port */
  1664.     if (PopupPixelDepth(popup) == 1 &&
  1665.          ! MenuItemEnabled((**popup).menu, (**popup).state.current))
  1666.     {
  1667.         selectionRect = (**popup).r.selection;
  1668.         RectDisable(&selectionRect);
  1669.     }
  1670. }
  1671.  
  1672. /* draw the title string */
  1673. static void PopupDrawTitle(PopupHandle popup)
  1674. {
  1675.     Str255 title;                /* title string */
  1676.     Rect rtitle;                /* title's text rectangle */
  1677.     Rect rhilite;                /* title's hilite rectangle */
  1678.     TextState saveText;        /* save text state */
  1679.     ColorState saveColor;    /* saved color state */
  1680.     GrafPtr port;                /* port we're drawing into */
  1681.     
  1682.     require((**popup).state.drawset);
  1683.     require(! (**popup).attr.typein);
  1684.  
  1685.     /* save port */
  1686.     GetPort(&port);
  1687.     GetTextState(&saveText);
  1688.     GetColorState(&saveColor);
  1689.     
  1690.     /* get rectangles */
  1691.     rhilite = (**popup).r.hilite;
  1692.     rtitle = (**popup).r.title;
  1693.     
  1694.     /* set the title's color */
  1695.     PopupPartColorSet(popup, kPopupPartTitle);
  1696.         
  1697.     /* set the text style */
  1698.     TextFace((**popup).attr.title.style);
  1699.     
  1700.     /* draw the title string */
  1701.     EraseRect(&rhilite);
  1702.     PopupTitle(popup, title);
  1703.     PopupDrawString(popup, title, &rtitle, (**popup).attr.just);
  1704.     
  1705.     /* restore port's settings */
  1706.     SetColorState(&saveColor);
  1707.     SetTextState(&saveText);
  1708. }
  1709.  
  1710. /* gray out menu if it's disabled */
  1711. static void PopupDrawEnabled(PopupHandle popup)
  1712. {
  1713.     Rect bounds;
  1714.     
  1715.     require(PopupValid(popup));
  1716.     require((**popup).state.drawset);
  1717.     if (PopupPixelDepth(popup) == 1 && ! (**popup).attr.enabled) {
  1718.         bounds = (**popup).r.bounds;
  1719.         RectDisable(&bounds);
  1720.     }
  1721. }
  1722.  
  1723. /* erase the entire popup menu */
  1724. static void PopupErase(PopupHandle popup)
  1725. {
  1726.     Rect bounds;
  1727.     
  1728.     require((**popup).state.drawset);
  1729.     bounds = (**popup).r.maxbounds;
  1730.     EraseRect(&bounds);
  1731. }
  1732.  
  1733. #if DRAW_RECTANGLES
  1734.  
  1735. /* draw frames around the parts of the popup; this is useful when
  1736.     debugging */
  1737. static void PopupDrawRectangles(PopupHandle popup)
  1738. {
  1739.     struct {
  1740.         long maxbounds;
  1741.         long frame;
  1742.         long shadow;
  1743.         long title;
  1744.         long hilite;
  1745.         long selection;
  1746.         long triangle;
  1747.     } partColor = {
  1748.         blackColor,
  1749.         yellowColor,
  1750.         magentaColor,
  1751.         redColor,
  1752.         cyanColor,
  1753.         greenColor,
  1754.         blueColor,
  1755.     };
  1756.     PenState penState;
  1757.     long foreColor;
  1758.     Rect shadow;
  1759.     Rect frame;
  1760.     GrafPtr port;
  1761.     
  1762.     /* setup port */
  1763.     GetPort(&port);
  1764.     GetPenState(&penState);
  1765.     foreColor = port->fgColor;
  1766.     PenNormal();
  1767.     
  1768.     /* draw outline of frame and shadow */
  1769.     if (kShadowSize > 2 && kFrameSize > 2) {
  1770.     
  1771.         /* calculate frame */
  1772.         frame = (**popup).r.bounds;    
  1773.         if ((**popup).attr.just == teFlushRight)
  1774.             frame.right = (**popup).r.selection.right + kFrameSize + kShadowSize;
  1775.         else
  1776.             frame.left = (**popup).r.selection.left - kFrameSize;
  1777.         frame.right -= kShadowSize;
  1778.         frame.bottom -= kShadowSize;
  1779.         
  1780.         /* draw outline of frame */
  1781.         ForeColor(partColor.frame);
  1782.         FrameRect(&frame);
  1783.         InsetRect(&frame, kFrameSize, kFrameSize);
  1784.         FrameRect(&frame);
  1785.         InsetRect(&frame, -kFrameSize, -kFrameSize);
  1786.         
  1787.         /* draw outline of shadow */
  1788.         ForeColor(partColor.shadow);
  1789.         shadow.top = frame.bottom;
  1790.         shadow.left = frame.left + kShadowOffset;
  1791.         shadow.bottom = shadow.top + kShadowSize;
  1792.         shadow.right = frame.right + kShadowSize;
  1793.         FrameRect(&shadow);
  1794.         shadow.top = frame.top + kShadowOffset;
  1795.         shadow.left = frame.right;
  1796.         shadow.bottom = frame.bottom + kShadowSize;
  1797.         shadow.right = shadow.left + kShadowSize;
  1798.         FrameRect(&shadow);
  1799.     }
  1800.  
  1801.     /* draw the other rectangles */
  1802.  
  1803.     ForeColor(partColor.maxbounds);
  1804.     frame = (**popup).r.maxbounds;
  1805.     FrameRect(&frame);
  1806.  
  1807.     ForeColor(partColor.title);
  1808.     frame = (**popup).r.title;
  1809.     FrameRect(&frame);
  1810.  
  1811.     ForeColor(partColor.hilite);
  1812.     frame = (**popup).r.hilite;
  1813.     FrameRect(&frame);
  1814.  
  1815.     ForeColor(partColor.selection);
  1816.     frame = (**popup).r.selection;
  1817.     FrameRect(&frame);
  1818.  
  1819.     ForeColor(partColor.triangle);
  1820.     frame = (**popup).r.triangle;
  1821.     FrameRect(&frame);
  1822.     
  1823.     /* restore port */
  1824.     SetPenState(&penState);
  1825.     ForeColor(foreColor);
  1826. }
  1827.  
  1828. #endif /* DRAW_RECTANGLES */
  1829.  
  1830. /* draw the menu into the current port */
  1831. static void PopupDrawDirect(PopupHandle popup)
  1832. {
  1833.     Boolean inserted; /* true if inserted menu into menu list */
  1834.     
  1835.     require(PopupValid(popup));
  1836.  
  1837.     /* setup drawing environment */
  1838.     PopupPortSetup(popup);
  1839.         
  1840.     /* erase the entire popup */
  1841.     PopupErase(popup);
  1842.     if ((**popup).attr.visible) {
  1843.     
  1844.         /* insert menu into menu list so we can access its color table */
  1845.         inserted = PopupInsertMenu(popup);
  1846.  
  1847.         #if DRAW_POPUP
  1848.             /* draw all the parts of the popup */
  1849.             if (! (**popup).attr.typein) {
  1850.                 PopupDrawTitle(popup);
  1851.                 PopupDrawSelection(popup);
  1852.             }
  1853.             PopupDrawFrame(popup);
  1854.             PopupDrawTriangle(popup);
  1855.             PopupDrawEnabled(popup);
  1856.         #endif /* DRAW_POPUP */
  1857.         
  1858.         #if DRAW_RECTANGLES
  1859.             PopupDrawRectangles(popup);
  1860.         #endif /* DRAW_RECTANGLES */
  1861.  
  1862.         /* remove the menu from the menu list */
  1863.         PopupDeleteMenu(popup, inserted);
  1864.     }
  1865.  
  1866.     /* restore drawing environment */
  1867.     PopupPortRestore(popup);
  1868. }
  1869.  
  1870. /* callback for DeviceLoop function when drawing without an offscreen bitmap */
  1871. static pascal void PopupDrawDeviceLoopCallBack(short depth, short flags,
  1872.     GDHandle device, long data)
  1873. {
  1874.     PopupHandle popup;
  1875.     
  1876.     popup = (PopupHandle) data;
  1877.     (**popup).draw.gdevice = device;
  1878.     (**popup).draw.gdepth = depth;
  1879.     PopupDrawDirect(popup);
  1880. }
  1881.  
  1882. /* Draw the popup using a device loop. The popup's utilRgn should be
  1883.     set to the region enclosing the popup's maxbound's rectangle, in
  1884.     global coordinates. For greater efficiency, the popup's utilRgn
  1885.     can be set to the intersection of the popup's bounding rectangle
  1886.     and its port's visRgn. */
  1887. static void PopupDrawDeviceLoop(PopupHandle popup)
  1888. {
  1889.     if (MacHasColorQD() && (**popup).draw.utilRgn) {
  1890.         DeviceLoop((**popup).draw.utilRgn,
  1891.             PopupDrawDeviceLoopCallBack, (long) popup, 0);
  1892.     }
  1893.     else {
  1894.         (**popup).draw.gdevice = NULL;
  1895.         PopupDrawDirect(popup);
  1896.     }
  1897. }
  1898.  
  1899. /* draw the popup to an offscreen bitmap, then copy the bitmap to the screen */
  1900. void PopupDraw(PopupHandle popup)
  1901. {
  1902.     CGrafPtr        saveCPort;        /* saved graphics port */
  1903.     GDHandle        saveGDH;            /* saved graphics device */
  1904.     Boolean        changed;            /* if true, image needs to be redrawn */
  1905.     Boolean        direct;            /* true if should draw to onscreen port */
  1906.     GWorldPtr    gworld;            /* temporary pointer to offscreen graphics world */
  1907.     GDHandle        gdevice;            /* temporary pointer to graphics device of gworld */
  1908.     short            gdepth;            /* temporary storage for depth of graphics world */
  1909.     Rect            popupRect;        /* popup's bounding rectangle */
  1910.     GWorldFlags    updateFlags;    /* flags returned UpdateGWorld */
  1911.     
  1912.     require(PopupValid(popup));
  1913.     
  1914.     /* don't draw popup if drawing is disabled */
  1915.     if ( ! (**popup).attr.draw)
  1916.         return;
  1917.     
  1918.     /* get the popup's changed flag; the popup is reimaged if it changed */
  1919.     changed = (**popup).state.changed;
  1920.     (**popup).state.changed = false;
  1921.     #if ! CACHE_OFFSCREEN
  1922.         changed = true;
  1923.     #endif
  1924.     
  1925.     /* calculate mask region for CopyBits */
  1926.     if ((**popup).draw.utilRgn) {
  1927.         check(EmptyRgn((**popup).draw.utilRgn));
  1928.         CopyRgn((**popup).port->clipRgn, (**popup).draw.utilRgn);
  1929.         SectRgn((**popup).port->visRgn, (**popup).draw.utilRgn,
  1930.             (**popup).draw.utilRgn);
  1931.     }
  1932.     
  1933.     /* try to draw from the offscreen graphics world */
  1934.     direct = true;
  1935.     (**popup).draw.gdepth = 0;
  1936.     (**popup).draw.gdevice = NULL;
  1937.     if ((**popup).draw.gworld) {
  1938.  
  1939.         /* use an offscreen graphics world */
  1940.         check(MacHasGWorlds());
  1941.         
  1942.         /* get the popup's bounding rectangle, in global coordinates */
  1943.         popupRect = (**popup).r.maxbounds;
  1944.         RectLocalToGlobal(&popupRect);
  1945.  
  1946.         /* Determine if the image spans multiple monitors with different
  1947.             pixel depths. If so, then using CopyBits to draw the image from
  1948.             an offscreen bitmap causes the image to be displayed poorly
  1949.             on the monitor with the smaller pixel depth. In particular,
  1950.             on a 1-bit screen, colors are mapped to black, and a disabled popup
  1951.             (grayed out) may not be displayed at all. While there may be some
  1952.             trick to getting offscreen graphics worlds to work well in this
  1953.             situation, for now we just use a regular device loop to draw the
  1954.             image to each screen device. */
  1955.         direct = false;
  1956.         if (MacHasColorQD()) {
  1957.             GDHandle mindevice;
  1958.             GDHandle maxdevice;
  1959.             mindevice = GetMinDevice(&popupRect);
  1960.             maxdevice = GetMaxDevice(&popupRect);
  1961.             if (    maxdevice &&
  1962.                     mindevice &&
  1963.                     mindevice != maxdevice &&
  1964.                     GetDeviceDepth(mindevice) !=
  1965.                     GetDeviceDepth(maxdevice))
  1966.             {
  1967.                 direct = true;
  1968.             }
  1969.         }
  1970.         
  1971.         if (! direct) {
  1972.         
  1973.             /* reset direct draw flag */
  1974.             direct = true;
  1975.             
  1976.             /* According to NIM:Imaging With QuickDraw, p6-9, we only
  1977.                 need to call UpdateGWorld if the pixmap has been purged,
  1978.                 after the window has been moved, and after update events.
  1979.                 While it is simple enough to determine if the pixmap has
  1980.                 been purged or if the window has been moved, it is not
  1981.                 easy for a CDEF to determine when an update event has
  1982.                 occurred. So, we always call UpdateGWorld whenever the
  1983.                 popup is redrawn. It doesn't hurt (except for a possible
  1984.                 performance hit) to call UpdateGWorld more than necessary. */
  1985.     
  1986.             /* update the offscreen graphics world */
  1987.             gworld = (**popup).draw.gworld;
  1988.             updateFlags = UpdateGWorld(&gworld, 0, &popupRect, NULL, NULL, 0);
  1989.             if (updateFlags != gwFlagErr) {
  1990.                 (**popup).draw.gworld = gworld;
  1991.                 
  1992.                 /* lock the offscreen pixmap */
  1993.                 if (LockPixels(GetPixMap(gworld))) {
  1994.                 
  1995.                     /* we're drawing offscreen */
  1996.                     direct = false;
  1997.                     
  1998.                     /* get the popup's bounding rectangle, in local coordinates */
  1999.                     popupRect = (**popup).r.maxbounds;
  2000.                     
  2001.                     /* determine if the popup changed, and redraw it to the
  2002.                         offscreen graphics world if necessary */
  2003.                     if (! changed)
  2004.                         changed = ((updateFlags & reallocPix) != 0);
  2005.                     if (changed) {
  2006.                         gdevice = GetGWorldDevice(gworld);
  2007.                         gdepth = (gdevice ? GetDeviceDepth(gdevice) : 1);
  2008.                         (**popup).draw.gdevice = gdevice;
  2009.                         (**popup).draw.gdepth = gdepth;
  2010.                         GetGWorld(&saveCPort, &saveGDH);
  2011.                         SetGWorld(gworld, NULL);
  2012.                         SetOrigin(popupRect.left, popupRect.top);
  2013.                         check(EqualRect(&gworld->portRect, &popupRect));
  2014.                         ClipRect(&popupRect);
  2015.                         PopupDrawDirect(popup);
  2016.                         SetGWorld(saveCPort, saveGDH);
  2017.                     }
  2018.                     
  2019.                     /* copy the offscreen pixmap to the onscreen port */
  2020.                     check(EqualRect(&gworld->portRect, &popupRect));
  2021.                     CopyBits(
  2022.                         &((GrafPtr) gworld)->portBits,
  2023.                         &(**popup).port->portBits,
  2024.                         &popupRect, &popupRect,
  2025.                         srcCopy, (**popup).draw.utilRgn);
  2026.                     
  2027.                     /* unlock the offscreen pixmap */
  2028.                     UnlockPixels(GetPixMap(gworld));
  2029.                 }
  2030.             }
  2031.         }
  2032.     }
  2033.  
  2034.     /* couldn't allocate an offscreen graphics world, or conditions weren't
  2035.         appropriate for drawing using the offscreen graphics world, but
  2036.         the popup can still be drawn directly to the onscreen port */
  2037.     if (direct)
  2038.         PopupDrawDeviceLoop(popup);
  2039.  
  2040.     /* clear the utility region to save memory */
  2041.     if ((**popup).draw.utilRgn)
  2042.         SetEmptyRgn((**popup).draw.utilRgn);
  2043. }
  2044.  
  2045. /* hilite the popup's title; useful for keyboard equivalents of commands */
  2046. void PopupHilite(PopupHandle popup)
  2047. {
  2048.     RGBColor foreColor;
  2049.     RGBColor backColor;
  2050.     RGBColor hiliteColor;
  2051.     short mode;
  2052.     Rect hilite;
  2053.     Boolean color;
  2054.     Boolean inserted;
  2055.     
  2056.     require(PopupValid(popup));
  2057.     if (    (**popup).attr.visible &&
  2058.             (**popup).attr.draw &&
  2059.             ! (**popup).attr.typein)
  2060.     {
  2061.         PopupPortSetup(popup);
  2062.         hilite = (**popup).r.hilite;
  2063.         inserted = PopupInsertMenu(popup);
  2064.         color = PopupPartColor(popup, kPopupPartTitle, &foreColor, &backColor, &mode);
  2065.         if (color) {
  2066.             GetHiliteColor((CGrafPtr) (**popup).port, &hiliteColor);
  2067.             HiliteColor(&foreColor);
  2068.             RGBForeColor(&foreColor);
  2069.             RGBBackColor(&backColor);
  2070.             LMSetHiliteMode(LMGetHiliteMode() & ~(1<<hiliteBit));
  2071.             InvertRect(&hilite);
  2072.             HiliteColor(&hiliteColor);
  2073.         }
  2074.         else
  2075.             InvertRect(&hilite);
  2076.         PopupDeleteMenu(popup, inserted);
  2077.         PopupPortRestore(popup);
  2078.     }
  2079. }
  2080.  
  2081. /*-------------------------------------------------------------------------*/
  2082. /* event handling */
  2083. /*-------------------------------------------------------------------------*/
  2084.  
  2085. /* A handle to this structure is installed in the menu's menuProc field. */
  2086. typedef struct {
  2087.     short jmp;                /* M68K jmp instruction */
  2088.     MenuDefProcPtr mdef;    /* address of our MDEF routine */
  2089.     Handle proc;            /* original MDEF handle */
  2090.     short width;            /* width of menu */
  2091. } PatchMenuProcStructure, **PatchMenuProcHandle;
  2092.  
  2093. /* Menu proc to patch the mSizeMsg, allowing us to set the menu width to
  2094.     include the triangle. This is installed just before calling
  2095.     PopupMenuSelect, and is removed immediately afterwards. */
  2096. static pascal void PatchMenuProc(short message, MenuHandle menu,
  2097.     Rect *menuRect, Point hitPt, short *whichItem)
  2098. {
  2099.     Handle proc;
  2100.     SignedByte state;
  2101.     PatchMenuProcHandle patch;
  2102.     
  2103.     patch = (PatchMenuProcHandle) (**menu).menuProc;    
  2104.     proc = (**patch).proc;
  2105.     state = HGetState(proc);
  2106.     MoveHHi(proc);
  2107.     HLock(proc);
  2108.     CallMenuDefProc((MenuDefProcPtr) *proc, message, menu, menuRect, hitPt, whichItem);
  2109.     if (message == mSizeMsg)
  2110.         (**menu).menuWidth = (**patch).width;
  2111.     HSetState(proc, state);
  2112. }
  2113.     
  2114. /* True if point (in local coordinates) is within the popup menu.
  2115.     Call this before calling PopupSelect. */
  2116. Boolean PopupWithin(PopupHandle popup, Point pt)
  2117. {
  2118.     Rect hilite;
  2119.     Rect selection;
  2120.     Boolean result;
  2121.     
  2122.     require(PopupValid(popup));
  2123.     result = false;
  2124.     if ((**popup).attr.visible && (**popup).attr.enabled) {
  2125.         hilite = (**popup).r.hilite;
  2126.         selection = (**popup).r.selection;
  2127.         result = (PtInRect(pt, &selection) || PtInRect(pt, &hilite));
  2128.     }
  2129.     return(result);
  2130. }
  2131.  
  2132. /* call this when there's a mouse down in a popup menu */
  2133. void PopupSelect(PopupHandle popup)
  2134. {
  2135.     long chosen;            /* item selected from menu */
  2136.     Point    location;        /* top left of menu */
  2137.     Boolean inserted;        /* true if inserted menu into menu list */
  2138.     Rect selection;        /* current selection rectangle */
  2139.     short oldMenuWidth;    /* saved menu width */
  2140.     Handle oldMenuProc;    /* saved menuProc */
  2141.     short menuWidth;        /* width of menu */
  2142.     PatchMenuProcHandle patch;
  2143.     
  2144.     require(PopupValid(popup));
  2145.     if (StillDown()) {
  2146.     
  2147.         /* insert menu into menu list */
  2148.         inserted = PopupInsertMenu(popup);
  2149.  
  2150.         /* hilite title */
  2151.         PopupHilite(popup);
  2152.         
  2153.         /* setup port */
  2154.         PopupPortSetup(popup);
  2155.         
  2156.         /* setup system font and size */
  2157.         PopupPortSetupSystem(popup);
  2158.         
  2159.         /* calculate position for popup menu */
  2160.         location.h = (**popup).r.selection.left;
  2161.         location.v = (**popup).r.selection.top;
  2162.         LocalToGlobal(&location);
  2163.                 
  2164.         /* adjust width of menu */
  2165.         CalcMenuSize((**popup).menu);
  2166.         oldMenuWidth = menuWidth = (**(**popup).menu).menuWidth;
  2167.         if (! (**popup).attr.typein) {
  2168.         
  2169.             /* make the menu wide enough to include the triangle */
  2170.             menuWidth += kTriangleWidth + kTriangleMargin;
  2171.             if ((**popup).attr.fixedwidth) {
  2172.                 /* make menu fill all of current selection rectangle */
  2173.                 menuWidth = (**popup).r.selection.right - (**popup).r.selection.left;
  2174.             }
  2175.             if (menuWidth < oldMenuWidth)
  2176.                 menuWidth = oldMenuWidth;
  2177.                 
  2178.             /* Though we patch the menu's mdef to ignore the width calculated
  2179.                 by the mSizeMsg message, we still need to explicitely set the
  2180.                 menu's width. This is necessary since PopupMenuSelect on
  2181.                 non-color macs (e.g., a Plus running system 7) doesn't send
  2182.                 the mSizeMsg message before drawing the menu, while on other
  2183.                 macs (e.g., a Quadra running system 7 Pro) the mSizeMsg is
  2184.                 sent by PopupMenuSelect. */
  2185.             (**(**popup).menu).menuWidth = menuWidth;
  2186.         }
  2187.         
  2188.         /* patch the menu's mdef to ignore the width calculated by
  2189.             the mSizeMsg message */
  2190.         oldMenuProc = (**(**popup).menu).menuProc;
  2191.         if ((**popup).menuProc) {
  2192.             patch = (PatchMenuProcHandle) (**popup).menuProc;
  2193.             (**patch).jmp = ASM_M68K_JMP;
  2194.             (**patch).mdef = PatchMenuProc;
  2195.             (**patch).proc = oldMenuProc;
  2196.             (**patch).width = menuWidth;
  2197.             (**(**popup).menu).menuProc = (**popup).menuProc;
  2198.         }
  2199.         
  2200.         /* let user select an item from the menu */
  2201.         chosen = PopUpMenuSelect((**popup).menu, location.v, location.h,
  2202.                                          (**popup).state.current);
  2203.         
  2204.         /* restore environment */
  2205.         (**(**popup).menu).menuProc = oldMenuProc;
  2206.         (**(**popup).menu).menuWidth = oldMenuWidth;
  2207.         PopupPortRestore(popup);
  2208.         PopupPortRestoreSystem(popup);
  2209.         
  2210.         /* unhilite the menu */
  2211.         PopupHilite(popup);
  2212.  
  2213.         /* remove the menu from the menu list */
  2214.         PopupDeleteMenu(popup, inserted);
  2215.  
  2216.         /* display the selected item */
  2217.         if (LoWord(chosen))
  2218.             PopupCurrentSet(popup, LoWord(chosen));
  2219.     }
  2220.     ensure(PopupValid(popup));
  2221. }
  2222.     
  2223. /*-------------------------------------------------------------------------*/
  2224. /* getting and setting attributes */
  2225. /*-------------------------------------------------------------------------*/
  2226.  
  2227. /* recalculate and redraw the popup menu */
  2228. static void PopupChanged(PopupHandle popup)
  2229. {
  2230.     (**popup).state.changed = true;
  2231.     PopupCalculate(popup);
  2232.     PopupDraw(popup);
  2233. }
  2234.  
  2235. /* return the version of the library that created the popup menu */
  2236. short PopupVersion(PopupHandle popup)
  2237. {
  2238.     return((**popup).version);
  2239. }
  2240.  
  2241. /* return the currently selected menu item */
  2242. short PopupCurrent(PopupHandle popup)
  2243. {
  2244.     require(PopupValid(popup));
  2245.     return((**popup).state.current);
  2246. }
  2247.  
  2248. /* set the currently selected menu item */
  2249. void PopupCurrentSet(PopupHandle popup, short current)
  2250. {
  2251.     short i, nitems;
  2252.     
  2253.     require(PopupValid(popup));
  2254.     nitems = CountMItems((**popup).menu);
  2255.     for (i = 1; i <= nitems; i++)
  2256.         SetItemMark((**popup).menu, i, noMark);
  2257.     SetItemMark((**popup).menu, current, (**popup).attr.mark);
  2258.     if (current != (**popup).state.current) {
  2259.         (**popup).state.current = current;
  2260.         PopupChanged(popup);
  2261.     }
  2262. }
  2263.  
  2264. /* turn drawing on or off */
  2265. void PopupDrawSet(PopupHandle popup, Boolean draw)
  2266. {
  2267.     require(PopupValid(popup));
  2268.     (**popup).attr.draw = draw;
  2269. }
  2270.  
  2271. /* make popup visible or invisible */
  2272. void PopupVisibleSet(PopupHandle popup, Boolean visible)
  2273. {    
  2274.     require(PopupValid(popup));    
  2275.     if (visible != (**popup).attr.visible) {
  2276.         (**popup).attr.visible = visible;
  2277.         PopupChanged(popup);
  2278.     }    
  2279. }
  2280.  
  2281. /* set the character used to mark the current menu item */
  2282. void PopupMarkSet(PopupHandle popup, char mark)
  2283. {
  2284.     require(PopupValid(popup));
  2285.     if (mark != (**popup).attr.mark) {
  2286.         (**popup).attr.mark = mark;
  2287.         SetItemMark((**popup).menu, (**popup).state.current, (**popup).attr.mark);
  2288.     }    
  2289. }
  2290.  
  2291. /* enable or disable the menu */
  2292. void PopupEnableSet(PopupHandle popup, Boolean enabled)
  2293. {
  2294.     require(PopupValid(popup));
  2295.     if (enabled != (**popup).attr.enabled) {
  2296.         (**popup).attr.enabled = enabled;
  2297.         PopupChanged(popup);
  2298.     }
  2299.     else if (! enabled) {
  2300.         /* the popup has to be reimaged or it won't get drawn properly
  2301.             when using CopyBits to a black and white grafport */
  2302.         PopupChanged(popup);
  2303.     }
  2304. }
  2305.  
  2306. /* turn type-in style popup menu on or off */
  2307. void PopupTypeInSet(PopupHandle popup, Boolean typein)
  2308. {
  2309.     require(PopupValid(popup));
  2310.     if (typein != (**popup).attr.typein) {
  2311.         (**popup).attr.typein = typein;
  2312.         PopupChanged(popup);
  2313.     }
  2314. }
  2315.  
  2316. /* return rectangle enclosing all of popup; this is the rectangle
  2317.     enclosing the parts of the popup that are actually visible, and
  2318.     may be smaller than the rectangle specified with PopupBoundsSet. */
  2319. void PopupBounds(PopupHandle popup, Rect *bounds)
  2320. {
  2321.     require(PopupValid(popup));
  2322.     *bounds = (**popup).r.bounds;
  2323.     ensure(RectValid(bounds));
  2324. }
  2325.  
  2326. /* set popup's maximum bounding rectangle; all drawing is clipped
  2327.     to this rectangle */
  2328. void PopupBoundsSet(PopupHandle popup, const Rect *maxbounds)
  2329. {
  2330.     Rect oldmax;
  2331.     
  2332.     require(PopupValid(popup));
  2333.     require(RectValid(maxbounds));
  2334.     oldmax = (**popup).r.maxbounds;
  2335.     if (! EqualRect(&oldmax, maxbounds)) {
  2336.         if ((**popup).attr.draw) {
  2337.             PopupPortSetup(popup);
  2338.             PopupErase(popup);
  2339.             PopupPortRestore(popup);
  2340.         }
  2341.         (**popup).r.maxbounds = *maxbounds;
  2342.         PopupChanged(popup);
  2343.     }
  2344. }
  2345.  
  2346. /* return popup's title string */
  2347. void PopupTitle(PopupHandle popup, Str255 title)
  2348. {
  2349.     *title = 0;
  2350.     if ((**popup).attr.title.str) {
  2351.         BlockMove(*(**popup).attr.title.str, title,
  2352.             **(**popup).attr.title.str + 1);
  2353.     }
  2354. }
  2355.  
  2356. /* set popup's title string */
  2357. void PopupTitleSet(PopupHandle popup, const Str255 title)
  2358. {
  2359.     Str255 oldtitle;
  2360.     
  2361.     require(PopupValid(popup));
  2362.     if ((**popup).attr.title.str) {
  2363.         PopupTitle(popup, oldtitle);
  2364.         if (! EqualString(title, oldtitle, true, true)) {
  2365.             PtrToXHand(title, (**popup).attr.title.str, *title + 1);
  2366.             PopupChanged(popup);
  2367.         }
  2368.     }
  2369. }
  2370.  
  2371. /* set width of popup's title; the title is resized dynamically
  2372.     if the width is zero */
  2373. void PopupTitleWidthSet(PopupHandle popup, short width)
  2374. {
  2375.     require(width >= 0);
  2376.     if (width != (**popup).attr.title.width) {
  2377.         (**popup).attr.title.width = width;
  2378.         PopupChanged(popup);
  2379.     }
  2380. }
  2381.  
  2382. /* set the text style in which the popup's title will be drawn */
  2383. void PopupTitleStyleSet(PopupHandle popup, Style style)
  2384. {
  2385.     if (style != (**popup).attr.title.style) {
  2386.         (**popup).attr.title.style = style;
  2387.         PopupChanged(popup);
  2388.     }
  2389. }
  2390.  
  2391. /* set whether the popup will use the window's font for drawing the
  2392.     title, current selection, and menu */    
  2393. void PopupUseWFontSet(PopupHandle popup, Boolean wfont)
  2394. {
  2395.     if (wfont != (**popup).attr.wfont) {
  2396.         (**popup).attr.wfont = wfont;
  2397.         PopupChanged(popup);
  2398.     }
  2399. }
  2400.  
  2401. /* set whether the popup will use a fixed width or will be resized
  2402.     dynamically */
  2403. void PopupFixedWidthSet(PopupHandle popup, Boolean fixedwidth)
  2404. {
  2405.     if (fixedwidth != (**popup).attr.fixedwidth) {
  2406.         (**popup).attr.fixedwidth = fixedwidth;
  2407.         PopupChanged(popup);
  2408.     }
  2409. }
  2410.  
  2411. /* set the justification style for drawing the popup */
  2412. void PopupJustSet(PopupHandle popup, short just)
  2413. {
  2414.     if (just != (**popup).attr.just) {
  2415.         (**popup).attr.just = just;
  2416.         PopupChanged(popup);
  2417.     }
  2418. }
  2419.  
  2420. /*-------------------------------------------------------------------------*/
  2421. /* allocation and disposal */
  2422. /*-------------------------------------------------------------------------*/
  2423.  
  2424. /*    Create a popup menu within the rectangle in the specified port.
  2425.     Drawing is initially off. Since the popup's title is initially empty,
  2426.     you should call PopupTitleSet if you want the popup to have a title.
  2427.     When you are finished configuring the popup, call PopupDrawSet to
  2428.     enable drawing and then call PopupCalculate. The popup's rectangles
  2429.     are only calculated when drawing is enabled. The popup menu allocates
  2430.     several utility handles, but will function, albeit not as well, even
  2431.     if it can't allocate any of the utility handles. To reduce flicker,
  2432.     the popup menu is drawn to an offscreen graphics world and then copied
  2433.     to the screen. The storage for the offscreen pixmap is kept in a
  2434.     relocatable and purgeable block. */
  2435. PopupHandle PopupBegin(GrafPtr port, MenuHandle menu,
  2436.     const Rect *maxbounds, ControlHandle ctl)
  2437. {
  2438.     PopupHandle popup;
  2439.     Rect globalbounds;
  2440.     void *tmp;
  2441.     
  2442.     require(menu != NULL);
  2443.     require(ctl != NULL);
  2444.     require(RectValid(maxbounds));
  2445.     
  2446.     /* allocate popup */
  2447.     popup = (PopupHandle) NewHandleClear(sizeof(PopupType));
  2448.     if (popup) {
  2449.     
  2450.         /* initialize internal state */
  2451.         (**popup).version = kPopupVersion;
  2452.         (**popup).port = port;
  2453.         (**popup).menu = menu;
  2454.         (**popup).ctl = ctl;
  2455.         (**popup).private.mHandle = menu;
  2456.         (**popup).private.mID = (**menu).menuID;
  2457.         (**popup).r.maxbounds = *maxbounds;
  2458.         (**popup).state.changed = true;
  2459.         
  2460.         /* initialize flags affecting display and operation of menu */
  2461.         (**popup).attr.visible = true;
  2462.         (**popup).attr.enabled = true;
  2463.         (**popup).attr.mark = checkMark;
  2464.         #if TEST_FLUSH_RIGHT
  2465.             (**popup).attr.just = teFlushRight;
  2466.         #else
  2467.             (**popup).attr.just = PopupJust(popup);
  2468.         #endif
  2469.         
  2470.         /* allocate title */
  2471.         tmp = NewHandleClear(1);
  2472.         (**popup).attr.title.str = tmp;
  2473.  
  2474.         /* allocate region for saving and restoring the clip region */
  2475.         tmp = NewRgn();
  2476.         (**popup).draw.save.clip = tmp;
  2477.         
  2478.         /* allocate glue handle for MDEF */
  2479.         tmp = NewHandle(sizeof(PatchMenuProcStructure));
  2480.         if (tmp)
  2481.             (**popup).menuProc = tmp;
  2482.     
  2483.         /* allocate utility region */
  2484.         tmp = NewRgn();
  2485.         (**popup).draw.utilRgn = tmp;
  2486.  
  2487.         /* Allocate an offscreen graphics world. The offscreen pixmap is
  2488.             purgeable, since we can always rebuild the data it contains and
  2489.             since we can always draw the popup to the onscreen port, though
  2490.             the image may flicker a bit. */
  2491.         #if DRAW_OFFSCREEN
  2492.             if (MacHasGWorlds()) {
  2493.                 Rect globalBounds;
  2494.                 GWorldPtr gworld;
  2495.                 GrafPtr savePort;
  2496.                 GetPort(&savePort);
  2497.                 SetPort((**popup).port);
  2498.                 globalBounds = (**popup).r.maxbounds;
  2499.                 RectLocalToGlobal(&globalBounds);
  2500.                 if (NewGWorld(&gworld, 0, &globalBounds, NULL, NULL, pixPurge) == noErr)
  2501.                     (**popup).draw.gworld = gworld;
  2502.                 SetPort(savePort);
  2503.             }
  2504.         #endif /* DRAW_OFFSCREEN */
  2505.     }
  2506.     ensure(! popup || PopupValid(popup));
  2507.     return(popup);
  2508. }
  2509.  
  2510. /* dispose of the popup menu */
  2511. void PopupEnd(PopupHandle popup)
  2512. {
  2513.     require(! popup || PopupValid(popup));
  2514.     if (popup) {
  2515.         if ((**popup).menuProc) DisposeHandle((**popup).menuProc);
  2516.         if ((**popup).draw.gworld) DisposeGWorld((**popup).draw.gworld);
  2517.         if ((**popup).draw.utilRgn) DisposeRgn((**popup).draw.utilRgn);
  2518.         if ((**popup).draw.save.clip) DisposeRgn((**popup).draw.save.clip);
  2519.         if ((**popup).attr.title.str) DisposeHandle((**popup).attr.title.str);
  2520.         DisposeHandle((Handle) popup);
  2521.         popup = NULL;
  2522.     }
  2523.     ensure(! PopupValid(popup));
  2524. }
  2525.